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() |
58
|
|
|
)); |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
$this->reflectClassToNode($class, $node); |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
foreach ($interfaces as $interface) { |
65
|
|
View Code Duplication |
if (!$interface instanceof ReflectionClass) { |
|
|
|
|
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() |
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() |
93
|
|
|
), $class); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
$node->setParentClass($class->getName()); |
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(), '_') |
108
|
|
|
&& !in_array($method->getName(), self::$reflectableMethods)) { |
109
|
|
|
continue; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
if (true === $method->isFinal()) { |
113
|
|
|
$node->addUnextendableMethod($method->getName()); |
114
|
|
|
continue; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
$this->reflectMethodToNode($method, $node); |
118
|
|
|
} |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
private function reflectInterfaceToNode(ReflectionClass $interface, Node\ClassNode $node) |
122
|
|
|
{ |
123
|
|
|
$node->addInterface($interface->getName()); |
124
|
|
|
|
125
|
|
|
foreach ($interface->getMethods() as $method) { |
126
|
|
|
$this->reflectMethodToNode($method, $node); |
127
|
|
|
} |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
private function reflectMethodToNode(ReflectionMethod $method, Node\ClassNode $classNode) |
131
|
|
|
{ |
132
|
|
|
$node = new Node\MethodNode($method->getName()); |
133
|
|
|
|
134
|
|
|
if (true === $method->isProtected()) { |
135
|
|
|
$node->setVisibility('protected'); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
if (true === $method->isStatic()) { |
139
|
|
|
$node->setStatic(); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
if (true === $method->returnsReference()) { |
143
|
|
|
$node->setReturnsReference(); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
if (version_compare(PHP_VERSION, '7.0', '>=') && $method->hasReturnType()) { |
147
|
|
|
$returnType = PHP_VERSION_ID >= 70100 ? $method->getReturnType()->getName() : (string) $method->getReturnType(); |
148
|
|
|
$returnTypeLower = strtolower($returnType); |
149
|
|
|
|
150
|
|
|
if ('self' === $returnTypeLower) { |
151
|
|
|
$returnType = $method->getDeclaringClass()->getName(); |
152
|
|
|
} |
153
|
|
|
if ('parent' === $returnTypeLower) { |
154
|
|
|
$returnType = $method->getDeclaringClass()->getParentClass()->getName(); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
$node->setReturnType($returnType); |
158
|
|
|
|
159
|
|
|
if (version_compare(PHP_VERSION, '7.1', '>=') && $method->getReturnType()->allowsNull()) { |
160
|
|
|
$node->setNullableReturnType(true); |
161
|
|
|
} |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
if (is_array($params = $method->getParameters()) && count($params)) { |
165
|
|
|
foreach ($params as $param) { |
166
|
|
|
$this->reflectArgumentToNode($param, $node); |
167
|
|
|
} |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
$classNode->addMethod($node); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
private function reflectArgumentToNode(ReflectionParameter $parameter, Node\MethodNode $methodNode) |
174
|
|
|
{ |
175
|
|
|
$name = $parameter->getName() == '...' ? '__dot_dot_dot__' : $parameter->getName(); |
176
|
|
|
$node = new Node\ArgumentNode($name); |
177
|
|
|
|
178
|
|
|
$node->setTypeHint($this->getTypeHint($parameter)); |
179
|
|
|
|
180
|
|
|
if ($this->isVariadic($parameter)) { |
181
|
|
|
$node->setAsVariadic(); |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
if ($this->hasDefaultValue($parameter)) { |
185
|
|
|
$node->setDefault($this->getDefaultValue($parameter)); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
if ($parameter->isPassedByReference()) { |
189
|
|
|
$node->setAsPassedByReference(); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
$node->setAsNullable($this->isNullable($parameter)); |
193
|
|
|
|
194
|
|
|
$methodNode->addArgument($node); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
private function hasDefaultValue(ReflectionParameter $parameter) |
198
|
|
|
{ |
199
|
|
|
if ($this->isVariadic($parameter)) { |
200
|
|
|
return false; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
if ($parameter->isDefaultValueAvailable()) { |
204
|
|
|
return true; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
return $parameter->isOptional() || $this->isNullable($parameter); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
private function getDefaultValue(ReflectionParameter $parameter) |
211
|
|
|
{ |
212
|
|
|
if (!$parameter->isDefaultValueAvailable()) { |
213
|
|
|
return null; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
return $parameter->getDefaultValue(); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
private function getTypeHint(ReflectionParameter $parameter) |
220
|
|
|
{ |
221
|
|
|
if (null !== $className = $this->getParameterClassName($parameter)) { |
222
|
|
|
return $className; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
if (true === $parameter->isArray()) { |
226
|
|
|
return 'array'; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
if (version_compare(PHP_VERSION, '5.4', '>=') && true === $parameter->isCallable()) { |
230
|
|
|
return 'callable'; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
if (version_compare(PHP_VERSION, '7.0', '>=') && true === $parameter->hasType()) { |
234
|
|
|
return PHP_VERSION_ID >= 70100 ? $parameter->getType()->getName() : (string) $parameter->getType(); |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
return null; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
private function isVariadic(ReflectionParameter $parameter) |
241
|
|
|
{ |
242
|
|
|
return PHP_VERSION_ID >= 50600 && $parameter->isVariadic(); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
private function isNullable(ReflectionParameter $parameter) |
246
|
|
|
{ |
247
|
|
|
return $parameter->allowsNull() && null !== $this->getTypeHint($parameter); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
private function getParameterClassName(ReflectionParameter $parameter) |
251
|
|
|
{ |
252
|
|
|
try { |
253
|
|
|
return $parameter->getClass() ? $parameter->getClass()->getName() : null; |
254
|
|
|
} catch (\ReflectionException $e) { |
255
|
|
|
preg_match('/\[\s\<\w+?>\s([\w,\\\]+)/s', $parameter, $matches); |
256
|
|
|
|
257
|
|
|
return isset($matches[1]) ? $matches[1] : null; |
258
|
|
|
} |
259
|
|
|
} |
260
|
|
|
} |
261
|
|
|
|
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.