4 * This file is part of the Prophecy.
5 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
6 * Marcello Duarte <marcello.duarte@gmail.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Prophecy\Doubler\Generator;
14 use Prophecy\Exception\InvalidArgumentException;
15 use Prophecy\Exception\Doubler\ClassMirrorException;
18 use ReflectionParameter;
22 * Core doubler class. Mirrors specific class and/or interfaces into class node tree.
24 * @author Konstantin Kudryashov <ever.zet@gmail.com>
28 private static $reflectableMethods = array(
39 * Reflects provided arguments into class node.
41 * @param ReflectionClass $class
42 * @param ReflectionClass[] $interfaces
44 * @return Node\ClassNode
46 * @throws \Prophecy\Exception\InvalidArgumentException
48 public function reflect(ReflectionClass $class = null, array $interfaces)
50 $node = new Node\ClassNode;
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.",
61 $this->reflectClassToNode($class, $node);
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)
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.",
80 $this->reflectInterfaceToNode($interface, $node);
83 $node->addInterface('Prophecy\Doubler\Generator\ReflectionInterface');
88 private function reflectClassToNode(ReflectionClass $class, Node\ClassNode $node)
90 if (true === $class->isFinal()) {
91 throw new ClassMirrorException(sprintf(
92 'Could not reflect class %s as it is marked final.', $class->getName()
96 $node->setParentClass($class->getName());
98 foreach ($class->getMethods(ReflectionMethod::IS_ABSTRACT) as $method) {
99 if (false === $method->isProtected()) {
103 $this->reflectMethodToNode($method, $node);
106 foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
107 if (0 === strpos($method->getName(), '_')
108 && !in_array($method->getName(), self::$reflectableMethods)) {
112 if (true === $method->isFinal()) {
113 $node->addUnextendableMethod($method->getName());
117 $this->reflectMethodToNode($method, $node);
121 private function reflectInterfaceToNode(ReflectionClass $interface, Node\ClassNode $node)
123 $node->addInterface($interface->getName());
125 foreach ($interface->getMethods() as $method) {
126 $this->reflectMethodToNode($method, $node);
130 private function reflectMethodToNode(ReflectionMethod $method, Node\ClassNode $classNode)
132 $node = new Node\MethodNode($method->getName());
134 if (true === $method->isProtected()) {
135 $node->setVisibility('protected');
138 if (true === $method->isStatic()) {
142 if (true === $method->returnsReference()) {
143 $node->setReturnsReference();
146 if (version_compare(PHP_VERSION, '7.0', '>=') && $method->hasReturnType()) {
147 $returnType = (string) $method->getReturnType();
148 $returnTypeLower = strtolower($returnType);
150 if ('self' === $returnTypeLower) {
151 $returnType = $method->getDeclaringClass()->getName();
153 if ('parent' === $returnTypeLower) {
154 $returnType = $method->getDeclaringClass()->getParentClass()->getName();
157 $node->setReturnType($returnType);
159 if (version_compare(PHP_VERSION, '7.1', '>=') && $method->getReturnType()->allowsNull()) {
160 $node->setNullableReturnType(true);
164 if (is_array($params = $method->getParameters()) && count($params)) {
165 foreach ($params as $param) {
166 $this->reflectArgumentToNode($param, $node);
170 $classNode->addMethod($node);
173 private function reflectArgumentToNode(ReflectionParameter $parameter, Node\MethodNode $methodNode)
175 $name = $parameter->getName() == '...' ? '__dot_dot_dot__' : $parameter->getName();
176 $node = new Node\ArgumentNode($name);
178 $node->setTypeHint($this->getTypeHint($parameter));
180 if ($this->isVariadic($parameter)) {
181 $node->setAsVariadic();
184 if ($this->hasDefaultValue($parameter)) {
185 $node->setDefault($this->getDefaultValue($parameter));
188 if ($parameter->isPassedByReference()) {
189 $node->setAsPassedByReference();
192 $methodNode->addArgument($node);
195 private function hasDefaultValue(ReflectionParameter $parameter)
197 if ($this->isVariadic($parameter)) {
201 if ($parameter->isDefaultValueAvailable()) {
205 return $parameter->isOptional() || $this->isNullable($parameter);
208 private function getDefaultValue(ReflectionParameter $parameter)
210 if (!$parameter->isDefaultValueAvailable()) {
214 return $parameter->getDefaultValue();
217 private function getTypeHint(ReflectionParameter $parameter)
219 if (null !== $className = $this->getParameterClassName($parameter)) {
223 if (true === $parameter->isArray()) {
227 if (version_compare(PHP_VERSION, '5.4', '>=') && true === $parameter->isCallable()) {
231 if (version_compare(PHP_VERSION, '7.0', '>=') && true === $parameter->hasType()) {
232 return (string) $parameter->getType();
238 private function isVariadic(ReflectionParameter $parameter)
240 return PHP_VERSION_ID >= 50600 && $parameter->isVariadic();
243 private function isNullable(ReflectionParameter $parameter)
245 return $parameter->allowsNull() && null !== $this->getTypeHint($parameter);
248 private function getParameterClassName(ReflectionParameter $parameter)
251 return $parameter->getClass() ? $parameter->getClass()->getName() : null;
252 } catch (\ReflectionException $e) {
253 preg_match('/\[\s\<\w+?>\s([\w,\\\]+)/s', $parameter, $matches);
255 return isset($matches[1]) ? $matches[1] : null;