Completed
Pull Request — master (#237)
by Kamil
02:50
created

ClassMirror::isNullable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 1
Metric Value
c 1
b 1
f 1
dl 0
loc 4
rs 10
cc 2
eloc 2
nc 2
nop 1
1
<?php
2
3
/*
4
 * This file is part of the Prophecy.
5
 * (c) Konstantin Kudryashov <[email protected]>
6
 *     Marcello Duarte <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Prophecy\Doubler\Generator;
13
14
use Prophecy\Exception\InvalidArgumentException;
15
use Prophecy\Exception\Doubler\ClassMirrorException;
16
use ReflectionClass;
17
use ReflectionMethod;
18
use ReflectionParameter;
19
20
/**
21
 * Class mirror.
22
 * Core doubler class. Mirrors specific class and/or interfaces into class node tree.
23
 *
24
 * @author Konstantin Kudryashov <[email protected]>
25
 */
26
class ClassMirror
27
{
28
    private static $reflectableMethods = array(
29
        '__construct',
30
        '__destruct',
31
        '__sleep',
32
        '__wakeup',
33
        '__toString',
34
        '__call',
35
        '__invoke'
36
    );
37
38
    /**
39
     * Reflects provided arguments into class node.
40
     *
41
     * @param ReflectionClass   $class
42
     * @param ReflectionClass[] $interfaces
43
     *
44
     * @return Node\ClassNode
45
     *
46
     * @throws \Prophecy\Exception\InvalidArgumentException
47
     */
48
    public function reflect(ReflectionClass $class = null, array $interfaces)
49
    {
50
        $node = new Node\ClassNode;
51
52
        if (null !== $class) {
53
            if (true === $class->isInterface()) {
54
                throw new InvalidArgumentException(sprintf(
55
                    "Could not reflect %s as a class, because it\n".
56
                    "is interface - use the second argument instead.",
57
                    $class->getName()
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
58
                ));
59
            }
60
61
            $this->reflectClassToNode($class, $node);
62
        }
63
64
        foreach ($interfaces as $interface) {
65 View Code Duplication
            if (!$interface instanceof ReflectionClass) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
66
                throw new InvalidArgumentException(sprintf(
67
                    "[ReflectionClass \$interface1 [, ReflectionClass \$interface2]] array expected as\n".
68
                    "a second argument to `ClassMirror::reflect(...)`, but got %s.",
69
                    is_object($interface) ? get_class($interface).' class' : gettype($interface)
70
                ));
71
            }
72
            if (false === $interface->isInterface()) {
73
                throw new InvalidArgumentException(sprintf(
74
                    "Could not reflect %s as an interface, because it\n".
75
                    "is class - use the first argument instead.",
76
                    $interface->getName()
0 ignored issues
show
Bug introduced by
Consider using $interface->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
77
                ));
78
            }
79
80
            $this->reflectInterfaceToNode($interface, $node);
81
        }
82
83
        $node->addInterface('Prophecy\Doubler\Generator\ReflectionInterface');
84
85
        return $node;
86
    }
87
88
    private function reflectClassToNode(ReflectionClass $class, Node\ClassNode $node)
89
    {
90
        if (true === $class->isFinal()) {
91
            throw new ClassMirrorException(sprintf(
92
                'Could not reflect class %s as it is marked final.', $class->getName()
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
93
            ), $class);
94
        }
95
96
        $node->setParentClass($class->getName());
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
97
98
        foreach ($class->getMethods(ReflectionMethod::IS_ABSTRACT) as $method) {
99
            if (false === $method->isProtected()) {
100
                continue;
101
            }
102
103
            $this->reflectMethodToNode($method, $node);
104
        }
105
106
        foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
107
            if (0 === strpos($method->getName(), '_')
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
108
                && !in_array($method->getName(), self::$reflectableMethods)) {
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
109
                continue;
110
            }
111
112
            if (true === $method->isFinal()) {
113
                continue;
114
            }
115
116
            $this->reflectMethodToNode($method, $node);
117
        }
118
    }
119
120
    private function reflectInterfaceToNode(ReflectionClass $interface, Node\ClassNode $node)
121
    {
122
        $node->addInterface($interface->getName());
0 ignored issues
show
Bug introduced by
Consider using $interface->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
123
124
        foreach ($interface->getMethods() as $method) {
125
            $this->reflectMethodToNode($method, $node);
126
        }
127
    }
128
129
    private function reflectMethodToNode(ReflectionMethod $method, Node\ClassNode $classNode)
130
    {
131
        $node = new Node\MethodNode($method->getName());
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
132
133
        if (true === $method->isProtected()) {
134
            $node->setVisibility('protected');
135
        }
136
137
        if (true === $method->isStatic()) {
138
            $node->setStatic();
139
        }
140
141
        if (true === $method->returnsReference()) {
142
            $node->setReturnsReference();
143
        }
144
145
        if (version_compare(PHP_VERSION, '7.0', '>=') && true === $method->hasReturnType()) {
146
            $node->setReturnType((string) $method->getReturnType());
147
        }
148
149
        if (is_array($params = $method->getParameters()) && count($params)) {
150
            foreach ($params as $param) {
151
                $this->reflectArgumentToNode($param, $node);
152
            }
153
        }
154
155
        $classNode->addMethod($node);
156
    }
157
158
    private function reflectArgumentToNode(ReflectionParameter $parameter, Node\MethodNode $methodNode)
159
    {
160
        $name = $parameter->getName() == '...' ? '__dot_dot_dot__' : $parameter->getName();
0 ignored issues
show
Bug introduced by
Consider using $parameter->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
161
        $node = new Node\ArgumentNode($name);
162
163
        $node->setTypeHint($this->getTypeHint($parameter));
164
165
        if ($this->isVariadic($parameter)) {
166
            $node->setAsVariadic();
167
        }
168
169
        if ($this->hasDefaultValue($parameter)) {
170
            $node->setDefault($this->getDefaultValue($parameter));
171
        }
172
173
        if ($parameter->isPassedByReference()) {
174
            $node->setAsPassedByReference();
175
        }
176
177
        $methodNode->addArgument($node);
178
    }
179
180
    private function hasDefaultValue(ReflectionParameter $parameter)
181
    {
182
        if ($this->isVariadic($parameter)) {
183
            return false;
184
        }
185
186
        if ($parameter->isDefaultValueAvailable()) {
187
            return true;
188
        }
189
190
        return $parameter->isOptional() || $this->isNullable($parameter);
191
    }
192
193
    private function getDefaultValue(ReflectionParameter $parameter)
194
    {
195
        if (!$parameter->isDefaultValueAvailable()) {
196
            return null;
197
        }
198
199
        return $parameter->getDefaultValue();
200
    }
201
202
    private function getTypeHint(ReflectionParameter $parameter)
203
    {
204
        if (null !== $className = $this->getParameterClassName($parameter)) {
205
            return $className;
206
        }
207
208
        if (true === $parameter->isArray()) {
209
            return 'array';
210
        }
211
212
        if (version_compare(PHP_VERSION, '5.4', '>=') && true === $parameter->isCallable()) {
213
            return 'callable';
214
        }
215
216
        if (version_compare(PHP_VERSION, '7.0', '>=') && true === $parameter->hasType()) {
217
            return (string) $parameter->getType();
218
        }
219
220
        return null;
221
    }
222
223
    private function isVariadic(ReflectionParameter $parameter)
224
    {
225
        return PHP_VERSION_ID >= 50600 && $parameter->isVariadic();
226
    }
227
228
    private function isNullable(ReflectionParameter $parameter)
229
    {
230
        return $parameter->allowsNull() && null !== $this->getTypeHint($parameter);
231
    }
232
233
    private function getParameterClassName(ReflectionParameter $parameter)
234
    {
235
        try {
236
            return $parameter->getClass() ? $parameter->getClass()->getName() : null;
0 ignored issues
show
Bug introduced by
Consider using $parameter->getClass()->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
237
        } catch (\ReflectionException $e) {
238
            preg_match('/\[\s\<\w+?>\s([\w,\\\]+)/s', $parameter, $matches);
239
240
            return isset($matches[1]) ? $matches[1] : null;
241
        }
242
    }
243
}
244