Completed
Push — master ( 555240...fa2155 )
by Alexander
115:41 queued 90:43
created

ReflectionMethod::__debugInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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 43
     * @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
    public function __construct(
52
        $className,
53
        $methodName,
54
        ClassMethod $classMethodNode = null,
55 43
        ReflectionClass $declaringClass = null
56 43
    ) {
57 43
        //for some reason, ReflectionMethod->getNamespaceName in php always returns '', so we shouldn't use it too
58
        $this->className        = ltrim($className, '\\');
59
        $this->declaringClass   = $declaringClass;
60 43
        $this->functionLikeNode = $classMethodNode ?: ReflectionEngine::parseClassMethod($className, $methodName);
61 43
62
        // Let's unset original read-only properties to have a control over them via __get
63
        unset($this->name, $this->class);
64
    }
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 1
     */
77
    public function __debugInfo(): array
78
    {
79 1
        return [
80 1
            'name'  => $this->getClassMethodNode()->name->toString(),
81
            '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 64
     */
92
    public function __toString()
93
    {
94 64
        // 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
        $methodParameters = $this->getParameters();
101 64
        try {
102 63
            $prototype = $this->getPrototype();
103 63
        } catch (\ReflectionException $e) {
104
            $prototype = null;
105 64
        }
106
        $prototypeClass = $prototype ? $prototype->getDeclaringClass()->name : '';
107 64
108 64
        $paramString = '';
109 64
        $indentation = str_repeat(' ', 4);
110 17
        foreach ($methodParameters as $methodParameter) {
111
            $paramString .= "\n{$indentation}" . $methodParameter;
112
        }
113 64
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
                    $this->getModifiers() & (self::IS_PUBLIC | self::IS_PROTECTED | self::IS_PRIVATE)
127
                )
128 64
            ),
129 64
            $this->getName(),
130 64
            $this->getFileName(),
131 64
            $this->getStartLine(),
132 64
            $this->getEndLine(),
133
            count($methodParameters),
134 64
            $paramString,
135
            $returnType ? ReflectionType::convertToDisplayType($returnType) : ''
136
        );
137
    }
138
139
    /**
140
     * {@inheritDoc}
141 1
     */
142
    public function getClosure($object = null)
143 1
    {
144
        $this->initializeInternalReflection();
145 1
146
        return parent::getClosure($object);
147
    }
148
149
    /**
150
     * {@inheritDoc}
151 135
     */
152
    public function getDeclaringClass()
153 135
    {
154
        return $this->declaringClass ?? new ReflectionClass($this->className);
155
    }
156
157
    /**
158
     * {@inheritDoc}
159 65
     */
160
    public function getModifiers()
161 65
    {
162 65
        $modifiers = 0;
163 51
        if ($this->isPublic()) {
164
            $modifiers += self::IS_PUBLIC;
165 65
        }
166 8
        if ($this->isProtected()) {
167
            $modifiers += self::IS_PROTECTED;
168 65
        }
169 8
        if ($this->isPrivate()) {
170
            $modifiers += self::IS_PRIVATE;
171 65
        }
172 8
        if ($this->isAbstract()) {
173
            $modifiers += self::IS_ABSTRACT;
174 65
        }
175 5
        if ($this->isFinal()) {
176
            $modifiers += self::IS_FINAL;
177 65
        }
178 10
        if ($this->isStatic()) {
179
            $modifiers += self::IS_STATIC;
180
        }
181 65
182
        return $modifiers;
183
    }
184
185
    /**
186
     * {@inheritDoc}
187 65
     */
188
    public function getPrototype()
189 65
    {
190 65
        $parent = $this->getDeclaringClass()->getParentClass();
191 48
        if (!$parent) {
192
            throw new ReflectionException("No prototype");
193
        }
194 17
195 2
        $prototypeMethod = $parent->getMethod($this->getName());
196
        if (!$prototypeMethod) {
197
            throw new ReflectionException("No prototype");
198
        }
199 2
200
        return $prototypeMethod;
201
    }
202
203
    /**
204
     * {@inheritDoc}
205 1
     */
206
    public function invoke($object, $args = null)
207 1
    {
208
        $this->initializeInternalReflection();
209 1
210
        return parent::invoke(...func_get_args());
211
    }
212
213
    /**
214
     * {@inheritDoc}
215 3
     */
216
    public function invokeArgs($object, array $args)
217 3
    {
218
        $this->initializeInternalReflection();
219 3
220
        return parent::invokeArgs($object, $args);
221
    }
222
223
    /**
224
     * {@inheritDoc}
225 129
     */
226
    public function isAbstract()
227 129
    {
228
        return $this->getDeclaringClass()->isInterface() || $this->getClassMethodNode()->isAbstract();
229
    }
230
231
    /**
232
     * {@inheritDoc}
233 128
     */
234
    public function isConstructor()
235 128
    {
236
        return $this->getClassMethodNode()->name->toLowerString() === '__construct';
237
    }
238
239
    /**
240
     * {@inheritDoc}
241 128
     */
242
    public function isDestructor()
243 128
    {
244
        return $this->getClassMethodNode()->name->toLowerString() === '__destruct';
245
    }
246
247
    /**
248
     * {@inheritDoc}
249 129
     */
250
    public function isFinal()
251 129
    {
252
        return $this->getClassMethodNode()->isFinal();
253
    }
254
255
    /**
256
     * {@inheritDoc}
257 129
     */
258
    public function isPrivate()
259 129
    {
260
        return $this->getClassMethodNode()->isPrivate();
261
    }
262
263
    /**
264
     * {@inheritDoc}
265 129
     */
266
    public function isProtected()
267 129
    {
268
        return $this->getClassMethodNode()->isProtected();
269
    }
270
271
    /**
272
     * {@inheritDoc}
273 132
     */
274
    public function isPublic()
275 132
    {
276
        return $this->getClassMethodNode()->isPublic();
277
    }
278
279
    /**
280
     * {@inheritDoc}
281 129
     */
282
    public function isStatic()
283 129
    {
284
        return $this->getClassMethodNode()->isStatic();
285
    }
286
287
    /**
288
     * {@inheritDoc}
289 1
     */
290
    public function setAccessible($accessible)
291 1
    {
292
        $this->initializeInternalReflection();
293 1
294 1
        parent::setAccessible($accessible);
295
    }
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 56
     */
305
    public static function collectFromClassNode(ClassLike $classLikeNode, ReflectionClass $reflectionClass): array
306 56
    {
307
        $methods = [];
308 56
309 53
        foreach ($classLikeNode->stmts as $classLevelNode) {
310 43
            if ($classLevelNode instanceof ClassMethod) {
311
                $classLevelNode->setAttribute('fileName', $classLikeNode->getAttribute('fileName'));
312 43
313 43
                $methodName = $classLevelNode->name->toString();
314 43
                $methods[$methodName] = new ReflectionMethod(
315
                    $reflectionClass->name,
316
                    $methodName,
317
                    $classLevelNode,
318
                    $reflectionClass
319
                );
320
            }
321
        }
322 56
323
        return $methods;
324
    }
325
326
    /**
327
     * Implementation of internal reflection initialization
328
     */
329
    protected function __initialize(): void
330 69
    {
331
        parent::__construct($this->className, $this->getName());
332 69
    }
333 69
334
    /**
335
     * Returns ClassMethod node to prevent all possible type checks with instanceof
336
     */
337
    private function getClassMethodNode(): ClassMethod
338
    {
339
        return $this->functionLikeNode;
340 578
    }
341
}
342