Completed
Push — master ( c4d2d4...eae51e )
by Alexander
9s
created

src/ReflectionMethod.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 46
    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 46
        $this->className        = $className;
56 46
        $this->declaringClass   = $declaringClass;
57 46
        $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 46
        unset($this->name, $this->class);
61 46
    }
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 63
    public function __toString()
82
    {
83 63
        $hasReturnType    = $this->hasReturnType();
84 63
        $paramsNeeded     = $hasReturnType || $this->getNumberOfParameters() > 0;
85 63
        $paramFormat      = $paramsNeeded ? "\n\n  - Parameters [%d] {%s\n  }" : '';
86 63
        $returnFormat     = $hasReturnType ? "\n  - Return [ %s ]" : '';
87 63
        $methodParameters = $this->getParameters();
88
        try {
89 63
            $prototype = $this->getPrototype();
90 62
        } catch (\ReflectionException $e) {
91 62
            $prototype = null;
92
        }
93 63
        $prototypeClass = $prototype ? $prototype->getDeclaringClass()->name : '';
94
95 63
        $paramString = '';
96 63
        $identation  = str_repeat(' ', 4);
97 63
        foreach ($methodParameters as $methodParameter) {
98 16
            $paramString .= "\n{$identation}" . $methodParameter;
99
        }
100
101 63
        return sprintf(
102 63
            "%sMethod [ <user%s%s%s>%s%s%s %s method %s ] {\n  @@ %s %d - %d{$paramFormat}{$returnFormat}\n}\n",
103 63
            $this->getDocComment() ? $this->getDocComment() . "\n" : '',
104 63
            $prototype ? ", overwrites {$prototypeClass}, prototype {$prototypeClass}" : '',
105 63
            $this->isConstructor() ? ', ctor' : '',
106 63
            $this->isDestructor() ? ', dtor' : '',
107 63
            $this->isFinal() ? ' final' : '',
108 63
            $this->isStatic() ? ' static' : '',
109 63
            $this->isAbstract() ? ' abstract' : '',
110 63
            join(' ', \Reflection::getModifierNames($this->getModifiers() & 1792)),
111 63
            $this->getName(),
112 63
            $this->getFileName(),
113 63
            $this->getStartLine(),
114 63
            $this->getEndLine(),
115
            count($methodParameters),
116
            $paramString,
117 63
            $hasReturnType ? ReflectionType::convertToDisplayType($this->getReturnType()) : ''
0 ignored issues
show
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 136
    public function getDeclaringClass()
135
    {
136 136
        return isset($this->declaringClass) ? $this->declaringClass : new ReflectionClass($this->className);
137
    }
138
139
    /**
140
     * {@inheritDoc}
141
     */
142 67
    public function getModifiers()
143
    {
144 67
        $modifiers = 0;
145 67
        if ($this->isPublic()) {
146 53
            $modifiers += self::IS_PUBLIC;
147
        }
148 67
        if ($this->isProtected()) {
149 9
            $modifiers += self::IS_PROTECTED;
150
        }
151 67
        if ($this->isPrivate()) {
152 9
            $modifiers += self::IS_PRIVATE;
153
        }
154 67
        if ($this->isAbstract()) {
155 9
            $modifiers += self::IS_ABSTRACT;
156
        }
157 67
        if ($this->isFinal()) {
158 6
            $modifiers += self::IS_FINAL;
159
        }
160 67
        if ($this->isStatic()) {
161 11
            $modifiers += self::IS_STATIC;
162
        }
163
164 67
        return $modifiers;
165
    }
166
167
    /**
168
     * {@inheritDoc}
169
     */
170 64
    public function getPrototype()
171
    {
172 64
        $parent = $this->getDeclaringClass()->getParentClass();
173 64
        if (!$parent) {
174 47
            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 130
    public function isAbstract()
209
    {
210 130
        return $this->getDeclaringClass()->isInterface() || $this->getClassMethodNode()->isAbstract();
211
    }
212
213
    /**
214
     * {@inheritDoc}
215
     */
216 126
    public function isConstructor()
217
    {
218 126
        return $this->getClassMethodNode()->name == '__construct';
219
    }
220
221
    /**
222
     * {@inheritDoc}
223
     */
224 126
    public function isDestructor()
225
    {
226 126
        return $this->getClassMethodNode()->name == '__destruct';
227
    }
228
229
    /**
230
     * {@inheritDoc}
231
     */
232 130
    public function isFinal()
233
    {
234 130
        return $this->getClassMethodNode()->isFinal();
235
    }
236
237
    /**
238
     * {@inheritDoc}
239
     */
240 130
    public function isPrivate()
241
    {
242 130
        return $this->getClassMethodNode()->isPrivate();
243
    }
244
245
    /**
246
     * {@inheritDoc}
247
     */
248 130
    public function isProtected()
249
    {
250 130
        return $this->getClassMethodNode()->isProtected();
251
    }
252
253
    /**
254
     * {@inheritDoc}
255
     */
256 133
    public function isPublic()
257
    {
258 133
        return $this->getClassMethodNode()->isPublic();
259
    }
260
261
    /**
262
     * {@inheritDoc}
263
     */
264 130
    public function isStatic()
265
    {
266 130
        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 60
    public static function collectFromClassNode(ClassLike $classLikeNode, ReflectionClass $reflectionClass)
288
    {
289 60
        $methods = [];
290
291 60
        foreach ($classLikeNode->stmts as $classLevelNode) {
292 57
            if ($classLevelNode instanceof ClassMethod) {
293 46
                $classLevelNode->setAttribute('fileName', $classLikeNode->getAttribute('fileName'));
294
295 46
                $methodName = $classLevelNode->name;
296 46
                $methods[$methodName] = new ReflectionMethod(
297 57
                    $reflectionClass->name,
298
                    $methodName,
299
                    $classLevelNode,
300
                    $reflectionClass
301
                );
302
            }
303
        }
304
305 60
        return $methods;
306
    }
307
308
    /**
309
     * Implementation of internal reflection initialization
310
     *
311
     * @return void
312
     */
313 68
    protected function __initialize()
314
    {
315 68
        parent::__construct($this->className, $this->getName());
316 68
    }
317
318
    /**
319
     * Returns ClassMethod node to prevent all possible type checks with instanceof
320
     *
321
     * @return ClassMethod
322
     */
323 572
    private function getClassMethodNode()
324
    {
325 572
        return $this->functionLikeNode;
326
    }
327
}
328