Completed
Push — master ( 5ecb26...a15224 )
by Alexander
10s
created

ReflectionMethod   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 306
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 8

Test Coverage

Coverage 99.06%

Importance

Changes 0
Metric Value
wmc 48
lcom 2
cbo 8
dl 0
loc 306
ccs 105
cts 106
cp 0.9906
c 0
b 0
f 0
rs 8.4864

21 Methods

Rating   Name   Duplication   Size   Complexity  
A ___debugInfo() 0 7 1
A __construct() 0 14 2
C __toString() 0 39 15
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 4 2
A isConstructor() 0 4 1
A isDestructor() 0 4 1
A isFinal() 0 4 1
A isPrivate() 0 4 1
A isProtected() 0 4 1
A isPublic() 0 4 1
A isStatic() 0 4 1
A setAccessible() 0 6 1
A collectFromClassNode() 0 20 3
A __initialize() 0 4 1
A getClassMethodNode() 0 4 1

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
19
/**
20
 * AST-based reflection for the method in a class
21
 */
22
class ReflectionMethod extends BaseReflectionMethod
23
{
24
    use ReflectionFunctionLikeTrait, InternalPropertiesEmulationTrait;
25
26
    /**
27
     * Name of the class
28
     *
29
     * @var string
30
     */
31
    private $className;
32
33
    /**
34
     * Optional declaring class reference
35
     *
36
     * @var ReflectionClass
37
     */
38
    private $declaringClass;
39
40
    /**
41
     * Initializes reflection instance for the method node
42
     *
43
     * @param string $className Name of the class
44
     * @param string $methodName Name of the method
45
     * @param ClassMethod $classMethodNode AST-node for method
46
     * @param ReflectionClass $declaringClass Optional declaring class
47
     */
48 38
    public function __construct(
49
        $className,
50
        $methodName,
51
        ClassMethod $classMethodNode = null,
52
        ReflectionClass $declaringClass = null
53
    ) {
54
        //for some reason, ReflectionMethod->getNamespaceName in php always returns '', so we shouldn't use it too
55 38
        $this->className        = $className;
56 38
        $this->declaringClass   = $declaringClass;
57 38
        $this->functionLikeNode = $classMethodNode ?: ReflectionEngine::parseClassMethod($className, $methodName);
58
59
        // Let's unset original read-only properties to have a control over them via __get
60 38
        unset($this->name, $this->class);
61 38
    }
62
63
    /**
64
     * Emulating original behaviour of reflection
65
     */
66 1
    public function ___debugInfo()
67
    {
68
        return [
69 1
            'name'  => $this->getClassMethodNode()->name,
70 1
            'class' => $this->className
71
        ];
72
    }
73
74
    /**
75
     * Returns the string representation of the Reflection method object.
76
     *
77
     * @link http://php.net/manual/en/reflectionmethod.tostring.php
78
     *
79
     * @return string
80
     */
81 49
    public function __toString()
82
    {
83 49
        $hasReturnType    = $this->hasReturnType();
84 49
        $paramsNeeded     = $hasReturnType || $this->getNumberOfParameters() > 0;
85 49
        $paramFormat      = $paramsNeeded ? "\n\n  - Parameters [%d] {%s\n  }" : '';
86 49
        $returnFormat     = $hasReturnType ? "\n  - Return [ %s ]" : '';
87 49
        $methodParameters = $this->getParameters();
88
        try {
89 49
            $prototype = $this->getPrototype();
90 48
        } catch (\ReflectionException $e) {
91 48
            $prototype = null;
92
        }
93 49
        $prototypeClass = $prototype ? $prototype->getDeclaringClass()->name : '';
94
95 49
        $paramString = '';
96 49
        $identation  = str_repeat(' ', 4);
97 49
        foreach ($methodParameters as $methodParameter) {
98 9
            $paramString .= "\n{$identation}" . $methodParameter;
99
        }
100
101 49
        return sprintf(
102 49
            "%sMethod [ <user%s%s%s>%s%s%s %s method %s ] {\n  @@ %s %d - %d{$paramFormat}{$returnFormat}\n}\n",
103 49
            $this->getDocComment() ? $this->getDocComment() . "\n" : '',
104 49
            $prototype ? ", overwrites {$prototypeClass}, prototype {$prototypeClass}" : '',
105 49
            $this->isConstructor() ? ', ctor' : '',
106 49
            $this->isDestructor() ? ', dtor' : '',
107 49
            $this->isFinal() ? ' final' : '',
108 49
            $this->isStatic() ? ' static' : '',
109 49
            $this->isAbstract() ? ' abstract' : '',
110 49
            join(' ', \Reflection::getModifierNames($this->getModifiers() & 1792)),
111 49
            $this->getName(),
112 49
            $this->getFileName(),
113 49
            $this->getStartLine(),
114 49
            $this->getEndLine(),
115
            count($methodParameters),
116
            $paramString,
117 49
            $hasReturnType ? ReflectionType::convertToDisplayType($this->getReturnType()) : ''
0 ignored issues
show
Bug introduced by
It seems like $this->getReturnType() can be null; however, convertToDisplayType() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
118
        );
119
    }
120
121
    /**
122
     * {@inheritDoc}
123
     */
124 1
    public function getClosure($object)
125
    {
126 1
        $this->initializeInternalReflection();
127
128 1
        return parent::getClosure($object);
129
    }
130
131
    /**
132
     * {@inheritDoc}
133
     */
134 107
    public function getDeclaringClass()
135
    {
136 107
        return isset($this->declaringClass) ? $this->declaringClass : new ReflectionClass($this->className);
137
    }
138
139
    /**
140
     * {@inheritDoc}
141
     */
142 52
    public function getModifiers()
143
    {
144 52
        $modifiers = 0;
145 52
        if ($this->isPublic()) {
146 38
            $modifiers += self::IS_PUBLIC;
147
        }
148 52
        if ($this->isProtected()) {
149 9
            $modifiers += self::IS_PROTECTED;
150
        }
151 52
        if ($this->isPrivate()) {
152 9
            $modifiers += self::IS_PRIVATE;
153
        }
154 52
        if ($this->isAbstract()) {
155 9
            $modifiers += self::IS_ABSTRACT;
156
        }
157 52
        if ($this->isFinal()) {
158 6
            $modifiers += self::IS_FINAL;
159
        }
160 52
        if ($this->isStatic()) {
161 11
            $modifiers += self::IS_STATIC;
162
        }
163
164 52
        return $modifiers;
165
    }
166
167
    /**
168
     * {@inheritDoc}
169
     */
170 50
    public function getPrototype()
171
    {
172 50
        $parent = $this->getDeclaringClass()->getParentClass();
173 50
        if (!$parent) {
174 33
            throw new ReflectionException("No prototype");
175
        }
176
177 17
        $prototypeMethod = $parent->getMethod($this->getName());
178 2
        if (!$prototypeMethod) {
179
            throw new ReflectionException("No prototype");
180
        }
181
182 2
        return $prototypeMethod;
183
    }
184
185
    /**
186
     * {@inheritDoc}
187
     */
188 1
    public function invoke($object, $args = null)
189
    {
190 1
        $this->initializeInternalReflection();
191
192 1
        return call_user_func_array('parent::invoke', func_get_args());
193
    }
194
195
    /**
196
     * {@inheritDoc}
197
     */
198 3
    public function invokeArgs($object, array $args)
199
    {
200 3
        $this->initializeInternalReflection();
201
202 3
        return parent::invokeArgs($object, $args);
203
    }
204
205
    /**
206
     * {@inheritDoc}
207
     */
208 101
    public function isAbstract()
209
    {
210 101
        return $this->getDeclaringClass()->isInterface() || $this->getClassMethodNode()->isAbstract();
211
    }
212
213
    /**
214
     * {@inheritDoc}
215
     */
216 98
    public function isConstructor()
217
    {
218 98
        return $this->getClassMethodNode()->name == '__construct';
219
    }
220
221
    /**
222
     * {@inheritDoc}
223
     */
224 98
    public function isDestructor()
225
    {
226 98
        return $this->getClassMethodNode()->name == '__destruct';
227
    }
228
229
    /**
230
     * {@inheritDoc}
231
     */
232 101
    public function isFinal()
233
    {
234 101
        return $this->getClassMethodNode()->isFinal();
235
    }
236
237
    /**
238
     * {@inheritDoc}
239
     */
240 101
    public function isPrivate()
241
    {
242 101
        return $this->getClassMethodNode()->isPrivate();
243
    }
244
245
    /**
246
     * {@inheritDoc}
247
     */
248 101
    public function isProtected()
249
    {
250 101
        return $this->getClassMethodNode()->isProtected();
251
    }
252
253
    /**
254
     * {@inheritDoc}
255
     */
256 104
    public function isPublic()
257
    {
258 104
        return $this->getClassMethodNode()->isPublic();
259
    }
260
261
    /**
262
     * {@inheritDoc}
263
     */
264 101
    public function isStatic()
265
    {
266 101
        return $this->getClassMethodNode()->isStatic();
267
    }
268
269
    /**
270
     * {@inheritDoc}
271
     */
272 1
    public function setAccessible($accessible)
273
    {
274 1
        $this->initializeInternalReflection();
275
276 1
        parent::setAccessible($accessible);
277 1
    }
278
279
    /**
280
     * Parses methods from the concrete class node
281
     *
282
     * @param ClassLike $classLikeNode Class-like node
283
     * @param ReflectionClass $reflectionClass Reflection of the class
284
     *
285
     * @return array|ReflectionMethod[]
286
     */
287 52
    public static function collectFromClassNode(ClassLike $classLikeNode, ReflectionClass $reflectionClass)
288
    {
289 52
        $methods = [];
290
291 52
        foreach ($classLikeNode->stmts as $classLevelNode) {
292 49
            if ($classLevelNode instanceof ClassMethod) {
293 38
                $classLevelNode->setAttribute('fileName', $classLikeNode->getAttribute('fileName'));
294
295 38
                $methodName = $classLevelNode->name;
296 38
                $methods[$methodName] = new ReflectionMethod(
297 49
                    $reflectionClass->name,
298
                    $methodName,
299
                    $classLevelNode,
300
                    $reflectionClass
301
                );
302
            }
303
        }
304
305 52
        return $methods;
306
    }
307
308
    /**
309
     * Implementation of internal reflection initialization
310
     *
311
     * @return void
312
     */
313 54
    protected function __initialize()
314
    {
315 54
        parent::__construct($this->className, $this->getName());
316 54
    }
317
318
    /**
319
     * Returns ClassMethod node to prevent all possible type checks with instanceof
320
     *
321
     * @return ClassMethod
322
     */
323 445
    private function getClassMethodNode()
324
    {
325 445
        return $this->functionLikeNode;
326
    }
327
}
328