Completed
Push — master ( 3078aa...588bdd )
by Konstantin
03:29 queued 01:31
created

ClassMirror::reflectMethodToNode()   C

Complexity

Conditions 11
Paths 80

Size

Total Lines 38
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 3
Metric Value
c 3
b 0
f 3
dl 0
loc 38
rs 5.2653
cc 11
eloc 20
nc 80
nop 2

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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) {
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()
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
                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());
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());
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
            $returnType = (string) $method->getReturnType();
147
            $returnTypeLower = strtolower($returnType);
148
149
            if ('self' === $returnTypeLower) {
150
                $returnType = $method->getDeclaringClass()->getName();
151
            }
152
            if ('parent' === $returnTypeLower) {
153
                $returnType = $method->getDeclaringClass()->getParentClass()->getName();
154
            }
155
156
            $node->setReturnType($returnType);
157
        }
158
159
        if (is_array($params = $method->getParameters()) && count($params)) {
160
            foreach ($params as $param) {
161
                $this->reflectArgumentToNode($param, $node);
162
            }
163
        }
164
165
        $classNode->addMethod($node);
166
    }
167
168
    private function reflectArgumentToNode(ReflectionParameter $parameter, Node\MethodNode $methodNode)
169
    {
170
        $name = $parameter->getName() == '...' ? '__dot_dot_dot__' : $parameter->getName();
171
        $node = new Node\ArgumentNode($name);
172
173
        $node->setTypeHint($this->getTypeHint($parameter));
174
175
        if ($this->isVariadic($parameter)) {
176
            $node->setAsVariadic();
177
        }
178
179
        if ($this->hasDefaultValue($parameter)) {
180
            $node->setDefault($this->getDefaultValue($parameter));
181
        }
182
183
        if ($parameter->isPassedByReference()) {
184
            $node->setAsPassedByReference();
185
        }
186
187
        $methodNode->addArgument($node);
188
    }
189
190
    private function hasDefaultValue(ReflectionParameter $parameter)
191
    {
192
        if ($this->isVariadic($parameter)) {
193
            return false;
194
        }
195
196
        if ($parameter->isDefaultValueAvailable()) {
197
            return true;
198
        }
199
200
        return $parameter->isOptional() || $this->isNullable($parameter);
201
    }
202
203
    private function getDefaultValue(ReflectionParameter $parameter)
204
    {
205
        if (!$parameter->isDefaultValueAvailable()) {
206
            return null;
207
        }
208
209
        return $parameter->getDefaultValue();
210
    }
211
212
    private function getTypeHint(ReflectionParameter $parameter)
213
    {
214
        if (null !== $className = $this->getParameterClassName($parameter)) {
215
            return $className;
216
        }
217
218
        if (true === $parameter->isArray()) {
219
            return 'array';
220
        }
221
222
        if (version_compare(PHP_VERSION, '5.4', '>=') && true === $parameter->isCallable()) {
223
            return 'callable';
224
        }
225
226
        if (version_compare(PHP_VERSION, '7.0', '>=') && true === $parameter->hasType()) {
227
            return (string) $parameter->getType();
228
        }
229
230
        return null;
231
    }
232
233
    private function isVariadic(ReflectionParameter $parameter)
234
    {
235
        return PHP_VERSION_ID >= 50600 && $parameter->isVariadic();
236
    }
237
238
    private function isNullable(ReflectionParameter $parameter)
239
    {
240
        return $parameter->allowsNull() && null !== $this->getTypeHint($parameter);
241
    }
242
243
    private function getParameterClassName(ReflectionParameter $parameter)
244
    {
245
        try {
246
            return $parameter->getClass() ? $parameter->getClass()->getName() : null;
247
        } catch (\ReflectionException $e) {
248
            preg_match('/\[\s\<\w+?>\s([\w,\\\]+)/s', $parameter, $matches);
249
250
            return isset($matches[1]) ? $matches[1] : null;
251
        }
252
    }
253
}
254