Completed
Pull Request — master (#107)
by Alexander
03:29
created

ReflectionMethod::invoke()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php
2
declare(strict_types=1);
3
/**
4
 * Parser Reflection API
5
 *
6
 * @copyright Copyright 2015, Lisachenko Alexander <[email protected]>
7
 *
8
 * This source file is subject to the license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Go\ParserReflection;
13
14
use Go\ParserReflection\Traits\InternalPropertiesEmulationTrait;
15
use Go\ParserReflection\Traits\ReflectionFunctionLikeTrait;
16
use PhpParser\Node\Stmt\ClassLike;
17
use PhpParser\Node\Stmt\ClassMethod;
18
use Reflection;
19
use ReflectionMethod as BaseReflectionMethod;
20
21
/**
22
 * AST-based reflection for the method in a class
23
 */
24
class ReflectionMethod extends BaseReflectionMethod
25
{
26
    use InternalPropertiesEmulationTrait;
27
    use ReflectionFunctionLikeTrait;
28
29
    /**
30
     * Name of the class
31
     *
32
     * @var string
33
     */
34
    private $className;
35
36
    /**
37
     * Optional declaring class reference
38
     *
39
     * @var ReflectionClass
40
     */
41
    private $declaringClass;
42
43
    /**
44
     * Initializes reflection instance for the method node
45
     *
46
     * @param string           $className       Name of the class
47
     * @param string           $methodName      Name of the method
48
     * @param ?ClassMethod     $classMethodNode AST-node for method
0 ignored issues
show
Documentation introduced by
The doc-type ?ClassMethod could not be parsed: Unknown type name "?ClassMethod" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
49
     * @param ?ReflectionClass $declaringClass  Optional declaring class
0 ignored issues
show
Documentation introduced by
The doc-type ?ReflectionClass could not be parsed: Unknown type name "?ReflectionClass" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
50
     */
51 43
    public function __construct(
52
        $className,
53
        $methodName,
54
        ClassMethod $classMethodNode = null,
55
        ReflectionClass $declaringClass = null
56
    ) {
57
        //for some reason, ReflectionMethod->getNamespaceName in php always returns '', so we shouldn't use it too
58 43
        $this->className        = ltrim($className, '\\');
59 43
        $this->declaringClass   = $declaringClass;
60 43
        $this->functionLikeNode = $classMethodNode ?: ReflectionEngine::parseClassMethod($className, $methodName);
61
62
        // Let's unset original read-only properties to have a control over them via __get
63 43
        unset($this->name, $this->class);
64 43
    }
65
66
    /**
67
     * Returns an AST-node for method
68
     */
69
    public function getNode(): ClassMethod
70
    {
71
        return $this->functionLikeNode;
72
    }
73
74
    /**
75
     * Emulating original behaviour of reflection
76
     */
77 1
    public function __debugInfo(): array
78
    {
79
        return [
80 1
            'name'  => $this->getClassMethodNode()->name->toString(),
81 1
            'class' => $this->className
82
        ];
83
    }
84
85
    /**
86
     * Returns the string representation of the Reflection method object.
87
     *
88
     * @link http://php.net/manual/en/reflectionmethod.tostring.php
89
     *
90
     * @return string
91
     */
92 64
    public function __toString()
93
    {
94
        // Internally $this->getReturnType() !== null is the same as $this->hasReturnType()
95 64
        $returnType       = $this->getReturnType();
96 64
        $hasReturnType    = $returnType !== null;
97 64
        $paramsNeeded     = $hasReturnType || $this->getNumberOfParameters() > 0;
98 64
        $paramFormat      = $paramsNeeded ? "\n\n  - Parameters [%d] {%s\n  }" : '';
99 64
        $returnFormat     = $hasReturnType ? "\n  - Return [ %s ]" : '';
100 64
        $methodParameters = $this->getParameters();
101
        try {
102 64
            $prototype = $this->getPrototype();
103 63
        } catch (\ReflectionException $e) {
104 63
            $prototype = null;
105
        }
106 64
        $prototypeClass = $prototype ? $prototype->getDeclaringClass()->name : '';
107
108 64
        $paramString = '';
109 64
        $indentation = str_repeat(' ', 4);
110 64
        foreach ($methodParameters as $methodParameter) {
111 17
            $paramString .= "\n{$indentation}" . $methodParameter;
112
        }
113
114 64
        return sprintf(
115 64
            "%sMethod [ <user%s%s%s>%s%s%s %s method %s ] {\n  @@ %s %d - %d{$paramFormat}{$returnFormat}\n}\n",
116 64
            $this->getDocComment() ? $this->getDocComment() . "\n" : '',
117 64
            $prototype ? ", overwrites {$prototypeClass}, prototype {$prototypeClass}" : '',
118 64
            $this->isConstructor() ? ', ctor' : '',
119 64
            $this->isDestructor() ? ', dtor' : '',
120 64
            $this->isFinal() ? ' final' : '',
121 64
            $this->isStatic() ? ' static' : '',
122 64
            $this->isAbstract() ? ' abstract' : '',
123 64
            implode(
124 64
                ' ',
125 64
                Reflection::getModifierNames(
126 64
                    $this->getModifiers() & (self::IS_PUBLIC | self::IS_PROTECTED | self::IS_PRIVATE)
127
                )
128
            ),
129 64
            $this->getName(),
130 64
            $this->getFileName(),
131 64
            $this->getStartLine(),
132 64
            $this->getEndLine(),
133 64
            count($methodParameters),
134
            $paramString,
135 64
            $returnType ? ReflectionType::convertToDisplayType($returnType) : ''
136
        );
137
    }
138
139
    /**
140
     * {@inheritDoc}
141
     */
142 1
    public function getClosure($object = null)
143
    {
144 1
        $this->initializeInternalReflection();
145
146 1
        return parent::getClosure($object);
147
    }
148
149
    /**
150
     * {@inheritDoc}
151
     */
152 135
    public function getDeclaringClass()
153
    {
154 135
        return $this->declaringClass ?? new ReflectionClass($this->className);
155
    }
156
157
    /**
158
     * {@inheritDoc}
159
     */
160 65
    public function getModifiers()
161
    {
162 65
        $modifiers = 0;
163 65
        if ($this->isPublic()) {
164 51
            $modifiers += self::IS_PUBLIC;
165
        }
166 65
        if ($this->isProtected()) {
167 8
            $modifiers += self::IS_PROTECTED;
168
        }
169 65
        if ($this->isPrivate()) {
170 8
            $modifiers += self::IS_PRIVATE;
171
        }
172 65
        if ($this->isAbstract()) {
173 8
            $modifiers += self::IS_ABSTRACT;
174
        }
175 65
        if ($this->isFinal()) {
176 5
            $modifiers += self::IS_FINAL;
177
        }
178 65
        if ($this->isStatic()) {
179 10
            $modifiers += self::IS_STATIC;
180
        }
181
182 65
        return $modifiers;
183
    }
184
185
    /**
186
     * {@inheritDoc}
187
     */
188 65
    public function getPrototype()
189
    {
190 65
        $parent = $this->getDeclaringClass()->getParentClass();
191 65
        if (!$parent) {
192 48
            throw new ReflectionException("No prototype");
193
        }
194
195 17
        $prototypeMethod = $parent->getMethod($this->getName());
196 2
        if (!$prototypeMethod) {
197
            throw new ReflectionException("No prototype");
198
        }
199
200 2
        return $prototypeMethod;
201
    }
202
203
    /**
204
     * {@inheritDoc}
205
     */
206 1
    public function invoke($object, $args = null)
207
    {
208 1
        $this->initializeInternalReflection();
209
210 1
        return parent::invoke(...func_get_args());
211
    }
212
213
    /**
214
     * {@inheritDoc}
215
     */
216 3
    public function invokeArgs($object, array $args)
217
    {
218 3
        $this->initializeInternalReflection();
219
220 3
        return parent::invokeArgs($object, $args);
221
    }
222
223
    /**
224
     * {@inheritDoc}
225
     */
226 129
    public function isAbstract()
227
    {
228 129
        return $this->getDeclaringClass()->isInterface() || $this->getClassMethodNode()->isAbstract();
229
    }
230
231
    /**
232
     * {@inheritDoc}
233
     */
234 128
    public function isConstructor()
235
    {
236 128
        return $this->getClassMethodNode()->name->toLowerString() === '__construct';
237
    }
238
239
    /**
240
     * {@inheritDoc}
241
     */
242 128
    public function isDestructor()
243
    {
244 128
        return $this->getClassMethodNode()->name->toLowerString() === '__destruct';
245
    }
246
247
    /**
248
     * {@inheritDoc}
249
     */
250 129
    public function isFinal()
251
    {
252 129
        return $this->getClassMethodNode()->isFinal();
253
    }
254
255
    /**
256
     * {@inheritDoc}
257
     */
258 129
    public function isPrivate()
259
    {
260 129
        return $this->getClassMethodNode()->isPrivate();
261
    }
262
263
    /**
264
     * {@inheritDoc}
265
     */
266 129
    public function isProtected()
267
    {
268 129
        return $this->getClassMethodNode()->isProtected();
269
    }
270
271
    /**
272
     * {@inheritDoc}
273
     */
274 132
    public function isPublic()
275
    {
276 132
        return $this->getClassMethodNode()->isPublic();
277
    }
278
279
    /**
280
     * {@inheritDoc}
281
     */
282 129
    public function isStatic()
283
    {
284 129
        return $this->getClassMethodNode()->isStatic();
285
    }
286
287
    /**
288
     * {@inheritDoc}
289
     */
290 1
    public function setAccessible($accessible)
291
    {
292 1
        $this->initializeInternalReflection();
293
294 1
        parent::setAccessible($accessible);
295 1
    }
296
297
    /**
298
     * Parses methods from the concrete class node
299
     *
300
     * @param ClassLike $classLikeNode Class-like node
301
     * @param ReflectionClass $reflectionClass Reflection of the class
302
     *
303
     * @return ReflectionMethod[]
304
     */
305 56
    public static function collectFromClassNode(ClassLike $classLikeNode, ReflectionClass $reflectionClass): array
306
    {
307 56
        $methods = [];
308
309 56
        foreach ($classLikeNode->stmts as $classLevelNode) {
310 53
            if ($classLevelNode instanceof ClassMethod) {
311 43
                $classLevelNode->setAttribute('fileName', $classLikeNode->getAttribute('fileName'));
312
313 43
                $methodName = $classLevelNode->name->toString();
314 43
                $methods[$methodName] = new ReflectionMethod(
315 43
                    $reflectionClass->name,
316
                    $methodName,
317
                    $classLevelNode,
318
                    $reflectionClass
319
                );
320
            }
321
        }
322
323 56
        return $methods;
324
    }
325
326
    /**
327
     * Implementation of internal reflection initialization
328
     */
329 69
    protected function __initialize(): void
330
    {
331 69
        parent::__construct($this->className, $this->getName());
332 69
    }
333
334
    /**
335
     * Returns ClassMethod node to prevent all possible type checks with instanceof
336
     */
337 578
    private function getClassMethodNode(): ClassMethod
338
    {
339 578
        return $this->functionLikeNode;
340
    }
341
}
342