Security update for Core, with self-updated composer
[yaffs-website] / vendor / phpspec / prophecy / src / Prophecy / Doubler / Generator / ClassMirror.php
1 <?php
2
3 /*
4  * This file is part of the Prophecy.
5  * (c) Konstantin Kudryashov <ever.zet@gmail.com>
6  *     Marcello Duarte <marcello.duarte@gmail.com>
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 <ever.zet@gmail.com>
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             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 = (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         $methodNode->addArgument($node);
193     }
194
195     private function hasDefaultValue(ReflectionParameter $parameter)
196     {
197         if ($this->isVariadic($parameter)) {
198             return false;
199         }
200
201         if ($parameter->isDefaultValueAvailable()) {
202             return true;
203         }
204
205         return $parameter->isOptional() || $this->isNullable($parameter);
206     }
207
208     private function getDefaultValue(ReflectionParameter $parameter)
209     {
210         if (!$parameter->isDefaultValueAvailable()) {
211             return null;
212         }
213
214         return $parameter->getDefaultValue();
215     }
216
217     private function getTypeHint(ReflectionParameter $parameter)
218     {
219         if (null !== $className = $this->getParameterClassName($parameter)) {
220             return $className;
221         }
222
223         if (true === $parameter->isArray()) {
224             return 'array';
225         }
226
227         if (version_compare(PHP_VERSION, '5.4', '>=') && true === $parameter->isCallable()) {
228             return 'callable';
229         }
230
231         if (version_compare(PHP_VERSION, '7.0', '>=') && true === $parameter->hasType()) {
232             return (string) $parameter->getType();
233         }
234
235         return null;
236     }
237
238     private function isVariadic(ReflectionParameter $parameter)
239     {
240         return PHP_VERSION_ID >= 50600 && $parameter->isVariadic();
241     }
242
243     private function isNullable(ReflectionParameter $parameter)
244     {
245         return $parameter->allowsNull() && null !== $this->getTypeHint($parameter);
246     }
247
248     private function getParameterClassName(ReflectionParameter $parameter)
249     {
250         try {
251             return $parameter->getClass() ? $parameter->getClass()->getName() : null;
252         } catch (\ReflectionException $e) {
253             preg_match('/\[\s\<\w+?>\s([\w,\\\]+)/s', $parameter, $matches);
254
255             return isset($matches[1]) ? $matches[1] : null;
256         }
257     }
258 }