Completed
Pull Request — master (#82)
by Loren
03:39
created

ReflectionMethod   C

Complexity

Total Complexity 66

Size/Duplication

Total Lines 403
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 97.52%

Importance

Changes 0
Metric Value
wmc 66
lcom 1
cbo 8
dl 0
loc 403
ccs 157
cts 161
cp 0.9752
rs 5.7474
c 0
b 0
f 0

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 21 4
A isParsedNodeMissing() 0 12 3
A getNode() 0 4 1
A ___debugInfo() 0 7 1
C __toString() 0 50 16
A getClosure() 0 6 1
A getDeclaringClass() 0 4 2
C getModifiers() 0 24 7
A getPrototype() 0 14 3
A invoke() 0 6 1
A invokeArgs() 0 6 1
A isAbstract() 0 8 3
A isConstructor() 0 8 2
A isDestructor() 0 8 2
A isFinal() 0 8 2
A isPrivate() 0 8 2
A isProtected() 0 8 2
A isPublic() 0 8 2
A isStatic() 0 8 2
A setAccessible() 0 6 1
A collectFromClassNode() 0 20 3
A __initialize() 0 4 1
A getClassMethodNode() 0 4 1
A wasIncluded() 0 7 3

How to fix   Complexity   

Complex Class

Complex classes like ReflectionMethod often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ReflectionMethod, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Parser Reflection API
4
 *
5
 * @copyright Copyright 2015, Lisachenko Alexander <[email protected]>
6
 *
7
 * This source file is subject to the license that is bundled
8
 * with this source code in the file LICENSE.
9
 */
10
11
namespace Go\ParserReflection;
12
13
use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait;
14
use Go\ParserReflection\Traits\ReflectionFunctionLikeTrait;
15
use PhpParser\Node\Stmt\ClassLike;
16
use PhpParser\Node\Stmt\ClassMethod;
17
use ReflectionMethod as BaseReflectionMethod;
18
use ReflectionClass as BaseReflectionClass;
19
20
/**
21
 * AST-based reflection for the method in a class
22
 */
23
class ReflectionMethod extends BaseReflectionMethod implements ReflectionInterface
24
{
25
    use ReflectionFunctionLikeTrait, InternalPropertiesEmulationTrait;
26
27
    /**
28
     * Name of the class
29
     *
30
     * @var string
31
     */
32
    private $className;
33
34
    /**
35
     * Optional declaring class reference
36
     *
37
     * @var ReflectionClass
38
     */
39
    private $declaringClass;
40
41
    /**
42
     * Name of method
43
     *
44
     * @var string
45
     */
46
    private $methodName;
47
48
    /**
49
     * Initializes reflection instance for the method node
50
     *
51
     * @param string $className Name of the class
52
     * @param string $methodName Name of the method
53
     * @param ClassMethod $classMethodNode AST-node for method
54
     * @param ReflectionClass $declaringClass Optional declaring class
55
     */
56 163
    public function __construct(
57
        $className,
58
        $methodName,
59
        ClassMethod $classMethodNode = null,
60
        ReflectionClass $declaringClass = null
61
    ) {
62
        //for some reason, ReflectionMethod->getNamespaceName in php always returns '', so we shouldn't use it too
63 163
        $this->className        = $className;
64 163
        $this->methodName       = $methodName;
65 163
        $this->declaringClass   = $declaringClass;
66 163
        $this->functionLikeNode = $classMethodNode;
67 163
        if ($this->isParsedNodeMissing()) {
68
            $this->functionLikeNode = ReflectionEngine::parseClassMethod($className, $methodName);
69
        }
70
        // Let's unset original read-only properties to have a control over them via __get
71 163
        unset($this->name, $this->class);
72
73 163
        if ($this->functionLikeNode && ($this->methodName !== $this->functionLikeNode->name)) {
74
            throw new \InvalidArgumentException("PhpParser\\Node\\Stmt\\ClassMethod's name does not match provided method name.");
75
        }
76 163
    }
77
78
    /**
79
     * Are we missing the parser node?
80
     */
81 163
    private function isParsedNodeMissing()
82
    {
83 163
        if ($this->functionLikeNode) {
84 139
            return false;
85
        }
86 24
        $isUserDefined = true;
87 24
        if ($this->wasIncluded()) {
88 24
            $nativeRef = new BaseReflectionClass($this->className);
89 24
            $isUserDefined = $nativeRef->isUserDefined();
90
        }
91 24
        return $isUserDefined;
92
    }
93
94
    /**
95
     * Returns an AST-node for method
96
     *
97
     * @return ClassMethod
98
     */
99
    public function getNode()
100
    {
101
        return $this->functionLikeNode;
102
    }
103
104
    /**
105
     * Emulating original behaviour of reflection
106
     */
107 21
    public function ___debugInfo()
108
    {
109
        return [
110 21
            'name'  => $this->methodName,
111 21
            'class' => $this->className
112
        ];
113
    }
114
115
    /**
116
     * Returns the string representation of the Reflection method object.
117
     *
118
     * @link http://php.net/manual/en/reflectionmethod.tostring.php
119
     *
120
     * @return string
121
     */
122 202
    public function __toString()
123
    {
124 202
        if (!$this->functionLikeNode) {
125 72
            $this->initializeInternalReflection();
126 72
            return parent::__toString();
127
        }
128
        // Internally $this->getReturnType() !== null is the same as $this->hasReturnType()
129 130
        $returnType       = $this->getReturnType();
130 130
        $hasReturnType    = $returnType !== null;
131 130
        $paramsNeeded     = $hasReturnType || $this->getNumberOfParameters() > 0;
132 130
        $paramFormat      = $paramsNeeded ? "\n\n  - Parameters [%d] {%s\n  }" : '';
133 130
        $returnFormat     = $hasReturnType ? "\n  - Return [ %s ]" : '';
134 130
        $methodParameters = $this->getParameters();
135
        try {
136 130
            $prototype = $this->getPrototype();
137 128
        } catch (\ReflectionException $e) {
138 128
            $prototype = null;
139
        }
140 130
        $prototypeClass = $prototype ? $prototype->getDeclaringClass()->name : '';
141
142 130
        $paramString = '';
143 130
        $identation  = str_repeat(' ', 4);
144 130
        foreach ($methodParameters as $methodParameter) {
145 34
            $paramString .= "\n{$identation}" . $methodParameter;
146
        }
147
148 130
        return sprintf(
149 130
            "%sMethod [ <user%s%s%s>%s%s%s %s method %s ] {\n  @@ %s %d - %d{$paramFormat}{$returnFormat}\n}\n",
150 130
            $this->getDocComment() ? $this->getDocComment() . "\n" : '',
151 130
            $prototype ? ", overwrites {$prototypeClass}, prototype {$prototypeClass}" : '',
152 130
            $this->isConstructor() ? ', ctor' : '',
153 130
            $this->isDestructor() ? ', dtor' : '',
154 130
            $this->isFinal() ? ' final' : '',
155 130
            $this->isStatic() ? ' static' : '',
156 130
            $this->isAbstract() ? ' abstract' : '',
157 130
            join(
158 130
                ' ',
159 130
                \Reflection::getModifierNames(
160 130
                    $this->getModifiers() & (self::IS_PUBLIC | self::IS_PROTECTED | self::IS_PRIVATE)
161
                )
162
            ),
163 130
            $this->getName(),
164 130
            $this->getFileName(),
165 130
            $this->getStartLine(),
166 130
            $this->getEndLine(),
167 130
            count($methodParameters),
168 130
            $paramString,
169 130
            $returnType ? ReflectionType::convertToDisplayType($returnType) : ''
170
        );
171
    }
172
173
    /**
174
     * {@inheritDoc}
175
     */
176 1
    public function getClosure($object)
177
    {
178 1
        $this->initializeInternalReflection();
179
180 1
        return parent::getClosure($object);
181
    }
182
183
    /**
184
     * {@inheritDoc}
185
     */
186 311
    public function getDeclaringClass()
187
    {
188 311
        return isset($this->declaringClass) ? $this->declaringClass : new ReflectionClass($this->className);
189
    }
190
191
    /**
192
     * {@inheritDoc}
193
     */
194 161
    public function getModifiers()
195
    {
196 161
        $modifiers = 0;
197 161
        if ($this->isPublic()) {
198 127
            $modifiers += self::IS_PUBLIC;
199
        }
200 161
        if ($this->isProtected()) {
201 21
            $modifiers += self::IS_PROTECTED;
202
        }
203 161
        if ($this->isPrivate()) {
204 23
            $modifiers += self::IS_PRIVATE;
205
        }
206 161
        if ($this->isAbstract()) {
207 21
            $modifiers += self::IS_ABSTRACT;
208
        }
209 161
        if ($this->isFinal()) {
210 13
            $modifiers += self::IS_FINAL;
211
        }
212 161
        if ($this->isStatic()) {
213 25
            $modifiers += self::IS_STATIC;
214
        }
215
216 161
        return $modifiers;
217
    }
218
219
    /**
220
     * {@inheritDoc}
221
     */
222 131
    public function getPrototype()
223
    {
224 131
        $parent = $this->getDeclaringClass()->getParentClass();
225 131
        if (!$parent) {
226 98
            throw new ReflectionException("No prototype");
227
        }
228
229 33
        $prototypeMethod = $parent->getMethod($this->getName());
230 33
        if (!$prototypeMethod) {
231 30
            throw new ReflectionException("No prototype");
232
        }
233
234 3
        return $prototypeMethod;
235
    }
236
237
    /**
238
     * {@inheritDoc}
239
     */
240 1
    public function invoke($object, $args = null)
241
    {
242 1
        $this->initializeInternalReflection();
243
244 1
        return call_user_func_array('parent::invoke', func_get_args());
245
    }
246
247
    /**
248
     * {@inheritDoc}
249
     */
250 3
    public function invokeArgs($object, array $args)
251
    {
252 3
        $this->initializeInternalReflection();
253
254 3
        return parent::invokeArgs($object, $args);
255
    }
256
257
    /**
258
     * {@inheritDoc}
259
     */
260 361
    public function isAbstract()
261
    {
262 361
        if (!$this->functionLikeNode) {
263 70
            $this->initializeInternalReflection();
264 70
            return parent::isAbstract();
265
        }
266 291
        return $this->getDeclaringClass()->isInterface() || $this->getClassMethodNode()->isAbstract();
267
    }
268
269
    /**
270
     * {@inheritDoc}
271
     */
272 330
    public function isConstructor()
273
    {
274 330
        if (!$this->functionLikeNode) {
275 70
            $this->initializeInternalReflection();
276 70
            return parent::isConstructor();
277
        }
278 260
        return $this->getClassMethodNode()->name == '__construct';
279
    }
280
281
    /**
282
     * {@inheritDoc}
283
     */
284 330
    public function isDestructor()
285
    {
286 330
        if (!$this->functionLikeNode) {
287 70
            $this->initializeInternalReflection();
288 70
            return parent::isDestructor();
289
        }
290 260
        return $this->getClassMethodNode()->name == '__destruct';
291
    }
292
293
    /**
294
     * {@inheritDoc}
295
     */
296 361
    public function isFinal()
297
    {
298 361
        if (!$this->functionLikeNode) {
299 70
            $this->initializeInternalReflection();
300 70
            return parent::isFinal();
301
        }
302 291
        return $this->getClassMethodNode()->isFinal();
303
    }
304
305
    /**
306
     * {@inheritDoc}
307
     */
308 372
    public function isPrivate()
309
    {
310 372
        if (!$this->functionLikeNode) {
311 70
            $this->initializeInternalReflection();
312 70
            return parent::isPrivate();
313
        }
314 302
        return $this->getClassMethodNode()->isPrivate();
315
    }
316
317
    /**
318
     * {@inheritDoc}
319
     */
320 361
    public function isProtected()
321
    {
322 361
        if (!$this->functionLikeNode) {
323 70
            $this->initializeInternalReflection();
324 70
            return parent::isProtected();
325
        }
326 291
        return $this->getClassMethodNode()->isProtected();
327
    }
328
329
    /**
330
     * {@inheritDoc}
331
     */
332 373
    public function isPublic()
333
    {
334 373
        if (!$this->functionLikeNode) {
335 76
            $this->initializeInternalReflection();
336 76
            return parent::isPublic();
337
        }
338 297
        return $this->getClassMethodNode()->isPublic();
339
    }
340
341
    /**
342
     * {@inheritDoc}
343
     */
344 363
    public function isStatic()
345
    {
346 363
        if (!$this->functionLikeNode) {
347 72
            $this->initializeInternalReflection();
348 72
            return parent::isStatic();
349
        }
350 291
        return $this->getClassMethodNode()->isStatic();
351
    }
352
353
    /**
354
     * {@inheritDoc}
355
     */
356 2
    public function setAccessible($accessible)
357
    {
358 2
        $this->initializeInternalReflection();
359
360 2
        parent::setAccessible($accessible);
361 2
    }
362
363
    /**
364
     * Parses methods from the concrete class node
365
     *
366
     * @param ClassLike $classLikeNode Class-like node
367
     * @param ReflectionClass $reflectionClass Reflection of the class
368
     *
369
     * @return array|ReflectionMethod[]
370
     */
371 207
    public static function collectFromClassNode(ClassLike $classLikeNode, ReflectionClass $reflectionClass)
372
    {
373 207
        $methods = [];
374
375 207
        foreach ($classLikeNode->stmts as $classLevelNode) {
376 185
            if ($classLevelNode instanceof ClassMethod) {
377 139
                $classLevelNode->setAttribute('fileName', $classLikeNode->getAttribute('fileName'));
378
379 139
                $methodName = $classLevelNode->name;
380 139
                $methods[$methodName] = new ReflectionMethod(
381 139
                    $reflectionClass->name,
382 139
                    $methodName,
383 139
                    $classLevelNode,
384 185
                    $reflectionClass
385
                );
386
            }
387
        }
388
389 207
        return $methods;
390
    }
391
392
    /**
393
     * Implementation of internal reflection initialization
394
     *
395
     * @return void
396
     */
397 155
    protected function __initialize()
398
    {
399 155
        parent::__construct($this->className, $this->methodName);
400 155
    }
401
402
    /**
403
     * Returns ClassMethod node to prevent all possible type checks with instanceof
404
     *
405
     * @return ClassMethod
406
     */
407 1212
    private function getClassMethodNode()
408
    {
409 1212
        return $this->functionLikeNode;
410
    }
411
412
    /**
413
     * Has class been loaded by PHP.
414
     *
415
     * @return bool
416
     *     If class file was included.
417
     */
418 24
    public function wasIncluded()
419
    {
420
        return
421 24
            interface_exists($this->className, false) ||
422 21
            trait_exists($this->className, false) ||
423 24
            class_exists($this->className, false);
424
    }
425
}
426