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

ReflectionParameter::__debugInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 1
cts 1
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\ValueResolver\NodeExpressionResolver;
16
use PhpParser\Node\Identifier;
17
use PhpParser\Node\Name;
18
use PhpParser\Node\NullableType;
19
use PhpParser\Node\Param;
20
use ReflectionFunctionAbstract;
21
use ReflectionParameter as BaseReflectionParameter;
22
23
/**
24
 * AST-based reflection for method/function parameter
25
 */
26
class ReflectionParameter extends BaseReflectionParameter
27
{
28
    use InternalPropertiesEmulationTrait;
29
30
    /**
31
     * Reflection function or method
32
     *
33
     * @var ReflectionFunctionAbstract
34
     */
35
    private $declaringFunction;
36
37
    /**
38
     * Stores the default value for node (if present)
39
     *
40
     * @var mixed
41
     */
42
    private $defaultValue;
43
44
    /**
45
     * Whether or not default value is constant
46
     *
47
     * @var bool
48
     */
49
    private $isDefaultValueConstant = false;
50
51
    /**
52
     * Name of the constant of default value
53
     *
54
     * @var string
55
     */
56
    private $defaultValueConstantName;
57
58
    /**
59
     * Index of parameter in the list
60
     *
61
     * @var int
62
     */
63
    private $parameterIndex;
64
65
    /**
66
     * Concrete parameter node
67
     *
68
     * @var Param
69
     */
70
    private $parameterNode;
71
72
    /**
73
     * Initializes a reflection for the property
74
     *
75
     * @param string|array                $unusedFunctionName Name of the function/method
76
     * @param string                      $parameterName      Name of the parameter to reflect
77
     * @param ?Param                      $parameterNode      Parameter definition node
0 ignored issues
show
Documentation introduced by
The doc-type ?Param could not be parsed: Unknown type name "?Param" 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...
78
     * @param int                         $parameterIndex     Index of parameter
79 32
     * @param ?ReflectionFunctionAbstract $declaringFunction
0 ignored issues
show
Documentation introduced by
The doc-type ?ReflectionFunctionAbstract could not be parsed: Unknown type name "?ReflectionFunctionAbstract" 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...
80
     */
81
    public function __construct(
82
        $unusedFunctionName,
83
        $parameterName,
84
        Param $parameterNode = null,
85
        $parameterIndex = 0,
86
        ReflectionFunctionAbstract $declaringFunction = null
87 32
    ) {
88
        // Let's unset original read-only property to have a control over it via __get
89 32
        unset($this->name);
90 32
91 32
        $this->parameterNode     = $parameterNode;
92
        $this->parameterIndex    = $parameterIndex;
93 32
        $this->declaringFunction = $declaringFunction;
94 20
95 10
        if ($this->isDefaultValueAvailable()) {
96
            if ($declaringFunction instanceof \ReflectionMethod) {
97 10
                $context = $declaringFunction->getDeclaringClass();
98
            } else {
99
                $context = $declaringFunction;
100 20
            }
101 20
102 20
            $expressionSolver = new NodeExpressionResolver($context);
103 20
            $expressionSolver->process($this->parameterNode->default);
104 20
            $this->defaultValue             = $expressionSolver->getValue();
105
            $this->isDefaultValueConstant   = $expressionSolver->isConstant();
106 32
            $this->defaultValueConstantName = $expressionSolver->getConstantName();
107
        }
108
    }
109
110
    /**
111
     * Returns an AST-node for parameter
112
     *
113
     * @return Param
114
     */
115
    public function getNode()
116
    {
117
        return $this->parameterNode;
118
    }
119
120
    /**
121 1
     * Emulating original behaviour of reflection
122
     */
123
    public function __debugInfo(): array
124 1
    {
125
        return [
126
            'name' => (string)$this->parameterNode->var->name,
127
        ];
128
    }
129
130
    /**
131
     * Returns string representation of this parameter.
132
     *
133 21
     * @return string
134
     */
135 21
    public function __toString()
136 21
    {
137 21
        $parameterType   = $this->getType();
138 21
        $isOptional      = $this->isOptional();
139 21
        $hasDefaultValue = $this->isDefaultValueAvailable();
140 10
        $defaultValue    = '';
141 10
        if ($hasDefaultValue) {
142 4
            $defaultValue = $this->getDefaultValue();
143
            if (is_string($defaultValue) && strlen($defaultValue) > 15) {
144
                $defaultValue = substr($defaultValue, 0, 15) . '...';
145 10
            }
146 3
            /* @see https://3v4l.org/DJOEb for behaviour changes */
147
            if (is_float($defaultValue) && fmod($defaultValue, 1.0) === 0.0) {
148
                $defaultValue = (int)$defaultValue;
149 10
            }
150
151
            $defaultValue = str_replace('\\\\', '\\', var_export($defaultValue, true));
152 21
        }
153 21
154 21
        return sprintf(
155 21
            'Parameter #%d [ %s %s%s%s$%s%s ]',
156 21
            $this->parameterIndex,
157 21
            $isOptional ? '<optional>' : '<required>',
158 21
            $parameterType ? ReflectionType::convertToDisplayType($parameterType) . ' ' : '',
159 21
            $this->isVariadic() ? '...' : '',
160 21
            $this->isPassedByReference() ? '&' : '',
161
            $this->getName(),
162
            ($isOptional && $hasDefaultValue) ? (' = ' . $defaultValue) : ''
163
        );
164
    }
165
166
    /**
167 22
     * {@inheritDoc}
168
     */
169
    public function allowsNull()
170 22
    {
171 6
        // Enable 7.1 nullable types support
172
        if ($this->parameterNode->type instanceof NullableType) {
173
            return true;
174 16
        }
175 16
176 4
        $hasDefaultNull = $this->isDefaultValueAvailable() && $this->getDefaultValue() === null;
177
        if ($hasDefaultNull) {
178
            return true;
179 14
        }
180
181
        return !isset($this->parameterNode->type);
182
    }
183
184
    /**
185 3
     * {@inheritDoc}
186
     */
187 3
    public function canBePassedByValue()
188
    {
189
        return !$this->isPassedByReference();
190
    }
191
192
    /**
193 2
     * @inheritDoc
194
     */
195 2
    public function getClass()
196 2
    {
197 2
        $parameterType = $this->parameterNode->type;
198 1
        if ($parameterType instanceof Name) {
199
            if (!$parameterType instanceof Name\FullyQualified) {
200 1
                $parameterTypeName = $parameterType->toString();
201 1
202
                if ('self' === $parameterTypeName) {
203
                    return $this->getDeclaringClass();
204 1
                }
205 1
206
                if ('parent' === $parameterTypeName) {
207
                    return $this->getDeclaringClass()->getParentClass();
208
                }
209
210 1
                throw new ReflectionException("Can not resolve a class name for parameter");
211 1
            }
212
            $className = $parameterType->toString();
213 1
214
            $classOrInterfaceExists = class_exists($className, false) || interface_exists($className, false);
215
216 1
            return $classOrInterfaceExists ? new \ReflectionClass($className) : new ReflectionClass($className);
217
        }
218
219
        return null;
220
    }
221
222 3
    /**
223
     * {@inheritDoc}
224 3
     */
225 2
    public function getDeclaringClass()
226
    {
227
        if ($this->declaringFunction instanceof \ReflectionMethod) {
228 1
            return $this->declaringFunction->getDeclaringClass();
229
        }
230
231
        return null;
232
    }
233
234 16
    /**
235
     * {@inheritDoc}
236 16
     */
237
    public function getDeclaringFunction()
238
    {
239
        return $this->declaringFunction;
240
    }
241
242 13
    /**
243
     * {@inheritDoc}
244 13
     */
245 1
    public function getDefaultValue()
246
    {
247
        if (!$this->isDefaultValueAvailable()) {
248 12
            throw new ReflectionException('Internal error: Failed to retrieve the default value');
249
        }
250
251
        return $this->defaultValue;
252
    }
253
254 6
    /**
255
     * {@inheritDoc}
256 6
     */
257 1
    public function getDefaultValueConstantName()
258
    {
259
        if (!$this->isDefaultValueAvailable()) {
260 5
            throw new ReflectionException('Internal error: Failed to retrieve the default value');
261
        }
262
263
        return $this->defaultValueConstantName;
264
    }
265
266 22
    /**
267
     * @inheritDoc
268 22
     */
269
    public function getName()
270
    {
271
        return (string)$this->parameterNode->var->name;
272
    }
273
274 3
    /**
275
     * {@inheritDoc}
276 3
     */
277
    public function getPosition()
278
    {
279
        return $this->parameterIndex;
280
    }
281
282 22
    /**
283
     * @inheritDoc
284 22
     */
285 22
    public function getType()
286 22
    {
287 6
        $isBuiltin     = false;
288
        $parameterType = $this->parameterNode->type;
289
        if ($parameterType instanceof NullableType) {
290 22
            $parameterType = $parameterType->type;
291 22
        }
292 18
293 18
        $allowsNull = $this->allowsNull();
294 8
        if ($parameterType instanceof Identifier) {
295 3
            $isBuiltin = true;
296 6
            $parameterType = $parameterType->toString();
297
        } elseif (is_object($parameterType)) {
298
            $parameterType = $parameterType->toString();
299 6
        } elseif (is_string($parameterType)) {
300
            $isBuiltin = true;
301
        } else {
302 18
            return null;
303
        }
304
305
        return new ReflectionType($parameterType, $allowsNull, $isBuiltin);
306
    }
307
308 4
    /**
309
     * @inheritDoc
310 4
     */
311
    public function hasType()
312 4
    {
313
        $hasType = isset($this->parameterNode->type);
314
315
        return $hasType;
316
    }
317
318 3
    /**
319
     * @inheritDoc
320 3
     */
321
    public function isArray()
322 3
    {
323
        $type = $this->parameterNode->type;
324
325
        return ($type instanceof Identifier) && 'array' === $type->name;
326
    }
327
328 3
    /**
329
     * @inheritDoc
330 3
     */
331
    public function isCallable()
332 3
    {
333
        $type = $this->parameterNode->type;
334
335
        return ($type instanceof Identifier) && 'callable' === $type->name;
336
    }
337
338 47
    /**
339
     * @inheritDoc
340 47
     */
341
    public function isDefaultValueAvailable()
342
    {
343
        return isset($this->parameterNode->default);
344
    }
345
346 5
    /**
347
     * {@inheritDoc}
348 5
     */
349
    public function isDefaultValueConstant()
350
    {
351
        return $this->isDefaultValueConstant;
352
    }
353
354 38
    /**
355
     * {@inheritDoc}
356 38
     */
357
    public function isOptional()
358
    {
359
        return $this->isVariadic() || ($this->isDefaultValueAvailable() && $this->haveSiblingsDefaultValues());
360
    }
361
362 21
    /**
363
     * @inheritDoc
364 21
     */
365
    public function isPassedByReference()
366
    {
367
        return (bool) $this->parameterNode->byRef;
368
    }
369
370 55
    /**
371
     * @inheritDoc
372 55
     */
373
    public function isVariadic()
374
    {
375
        return (bool) $this->parameterNode->variadic;
376
    }
377
378
    /**
379
     * Returns if all following parameters have a default value definition.
380
     *
381 16
     * @return bool
382
     * @throws ReflectionException If could not fetch declaring function reflection
383 16
     */
384 16
    protected function haveSiblingsDefaultValues()
385
    {
386
        $function = $this->getDeclaringFunction();
387
        if (null === $function) {
388
            throw new ReflectionException('Could not get the declaring function reflection.');
389 16
        }
390 16
391 14
        /** @var BaseReflectionParameter[] $remainingParameters */
392 2
        $remainingParameters = array_slice($function->getParameters(), $this->parameterIndex + 1);
393
        foreach ($remainingParameters as $reflectionParameter) {
394
            if (!$reflectionParameter->isDefaultValueAvailable()) {
395
                return false;
396 15
            }
397
        }
398
399
        return true;
400
    }
401
}
402