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

src/ReflectionParameter.php (1 issue)

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\ValueResolver\NodeExpressionResolver;
15
use PhpParser\Node\Name;
16
use PhpParser\Node\NullableType;
17
use PhpParser\Node\Param;
18
use ReflectionParameter as BaseReflectionParameter;
19
20
/**
21
 * AST-based reflection for method/function parameter
22
 */
23
class ReflectionParameter extends BaseReflectionParameter
24
{
25
    use InternalPropertiesEmulationTrait;
26
27
    /**
28
     * Reflection function or method
29
     *
30
     * @var \ReflectionFunctionAbstract
31
     */
32
    private $declaringFunction;
33
34
    /**
35
     * Stores the default value for node (if present)
36
     *
37
     * @var mixed
38
     */
39
    private $defaultValue = null;
40
41
    /**
42
     * Whether or not default value is constant
43
     *
44
     * @var bool
45
     */
46
    private $isDefaultValueConstant = false;
47
48
    /**
49
     * Name of the constant of default value
50
     *
51
     * @var string
52
     */
53
    private $defaultValueConstantName;
54
55
    /**
56
     * Index of parameter in the list
57
     *
58
     * @var int
59
     */
60
    private $parameterIndex = 0;
61
62
    /**
63
     * Concrete parameter node
64
     *
65
     * @var Param
66
     */
67
    private $parameterNode;
68
69
    /**
70
     * Initializes a reflection for the property
71
     *
72
     * @param string|array $unusedFunctionName Name of the function/method
73
     * @param string $parameterName Name of the parameter to reflect
74
     * @param Param $parameterNode Parameter definition node
75
     * @param int $parameterIndex Index of parameter
76
     * @param \ReflectionFunctionAbstract $declaringFunction
77
     */
78 31
    public function __construct(
79
        $unusedFunctionName,
80
        $parameterName,
81
        Param $parameterNode = null,
82
        $parameterIndex = 0,
83
        \ReflectionFunctionAbstract $declaringFunction = null
84
    ) {
85
        // Let's unset original read-only property to have a control over it via __get
86 31
        unset($this->name);
87
88 31
        $this->parameterNode     = $parameterNode;
89 31
        $this->parameterIndex    = $parameterIndex;
90 31
        $this->declaringFunction = $declaringFunction;
91
92 31
        if ($this->isDefaultValueAvailable()) {
93 19
            if ($declaringFunction instanceof \ReflectionMethod) {
94 9
                $context = $declaringFunction->getDeclaringClass();
95
            } else {
96 10
                $context = $declaringFunction;
97
            };
98
99 19
            $expressionSolver = new NodeExpressionResolver($context);
100 19
            $expressionSolver->process($this->parameterNode->default);
101 19
            $this->defaultValue             = $expressionSolver->getValue();
102 19
            $this->isDefaultValueConstant   = $expressionSolver->isConstant();
103 19
            $this->defaultValueConstantName = $expressionSolver->getConstantName();
104
        }
105 31
    }
106
107
    /**
108
     * Emulating original behaviour of reflection
109
     */
110 1
    public function ___debugInfo()
111
    {
112
        return array(
113 1
            'name' => $this->parameterNode->name,
114
        );
115
    }
116
117
    /**
118
     * Returns string representation of this parameter.
119
     *
120
     * @return string
121
     */
122 20
    public function __toString()
123
    {
124 20
        $parameterType   = $this->getType();
125 20
        $isNullableParam = !empty($parameterType) && $this->allowsNull();
0 ignored issues
show
$isNullableParam is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
126 20
        $isOptional      = $this->isOptional();
127 20
        $hasDefaultValue = $this->isDefaultValueAvailable();
128 20
        $defaultValue    = '';
129 20
        if ($hasDefaultValue) {
130 9
            $defaultValue = $this->getDefaultValue();
131 9
            if (is_string($defaultValue) && strlen($defaultValue) > 15) {
132 4
                $defaultValue = substr($defaultValue, 0, 15) . '...';
133
            }
134
            /* @see https://3v4l.org/DJOEb for behaviour changes */
135 9
            if (is_double($defaultValue) && fmod($defaultValue, 1.0) === 0.0) {
136 3
                $defaultValue = (int) $defaultValue;
137
            }
138
139 9
            $defaultValue = str_replace('\\\\', '\\', var_export($defaultValue, true));
140
        }
141
142 20
        return sprintf(
143 20
            'Parameter #%d [ %s %s%s%s$%s%s ]',
144 20
            $this->parameterIndex,
145 20
            $isOptional ? '<optional>' : '<required>',
146 20
            $parameterType ? ReflectionType::convertToDisplayType($parameterType) . ' ' : '',
147 20
            $this->isVariadic() ? '...' : '',
148 20
            $this->isPassedByReference() ? '&' : '',
149 20
            $this->getName(),
150 20
            ($isOptional && $hasDefaultValue) ? (' = ' . $defaultValue) : ''
151
        );
152
    }
153
154
    /**
155
     * {@inheritDoc}
156
     */
157 21
    public function allowsNull()
158
    {
159
        // Enable 7.1 nullable types support
160 21
        if ($this->parameterNode->type instanceof NullableType) {
161 6
            return true;
162
        }
163
164 15
        $hasDefaultNull = $this->isDefaultValueAvailable() && $this->getDefaultValue() === null;
165 15
        if ($hasDefaultNull) {
166 3
            return true;
167
        }
168
169 14
        return !isset($this->parameterNode->type);
170
    }
171
172
    /**
173
     * {@inheritDoc}
174
     */
175 3
    public function canBePassedByValue()
176
    {
177 3
        return !$this->isPassedByReference();
178
    }
179
180
    /**
181
     * @inheritDoc
182
     */
183 2
    public function getClass()
184
    {
185 2
        $parameterType = $this->parameterNode->type;
186 2
        if ($parameterType instanceof Name) {
187 2
            if (!$parameterType instanceof Name\FullyQualified) {
188 1
                $parameterTypeName = $parameterType->toString();
189
190 1
                if ('self' === $parameterTypeName) {
191 1
                    return $this->getDeclaringClass();
192
                }
193
194 1
                if ('parent' === $parameterTypeName) {
195 1
                    return $this->getDeclaringClass()->getParentClass();
196
                }
197
198
                throw new ReflectionException("Can not resolve a class name for parameter");
199
            }
200 1
            $className   = $parameterType->toString();
201 1
            $classOrInterfaceExists = class_exists($className, false) || interface_exists($className, false);
202
203 1
            return $classOrInterfaceExists ? new \ReflectionClass($className) : new ReflectionClass($className);
204
        }
205
206 1
        return null;
207
    }
208
209
    /**
210
     * {@inheritDoc}
211
     */
212 3
    public function getDeclaringClass()
213
    {
214 3
        if ($this->declaringFunction instanceof \ReflectionMethod) {
215 2
            return $this->declaringFunction->getDeclaringClass();
216
        };
217
218 1
        return null;
219
    }
220
221
    /**
222
     * {@inheritDoc}
223
     */
224 14
    public function getDeclaringFunction()
225
    {
226 14
        return $this->declaringFunction;
227
    }
228
229
    /**
230
     * {@inheritDoc}
231
     */
232 12
    public function getDefaultValue()
233
    {
234 12
        if (!$this->isDefaultValueAvailable()) {
235 1
            throw new ReflectionException('Internal error: Failed to retrieve the default value');
236
        }
237
238 11
        return $this->defaultValue;
239
    }
240
241
    /**
242
     * {@inheritDoc}
243
     */
244 6
    public function getDefaultValueConstantName()
245
    {
246 6
        if (!$this->isDefaultValueAvailable()) {
247 1
            throw new ReflectionException('Internal error: Failed to retrieve the default value');
248
        }
249
250 5
        return $this->defaultValueConstantName;
251
    }
252
253
    /**
254
     * @inheritDoc
255
     */
256 21
    public function getName()
257
    {
258 21
        return $this->parameterNode->name;
259
    }
260
261
    /**
262
     * {@inheritDoc}
263
     */
264 3
    public function getPosition()
265
    {
266 3
        return $this->parameterIndex;
267
    }
268
269
    /**
270
     * @inheritDoc
271
     */
272 21
    public function getType()
273
    {
274 21
        $isBuiltin     = false;
275 21
        $parameterType = $this->parameterNode->type;
276 21
        if ($parameterType instanceof NullableType) {
277 6
            $parameterType = $parameterType->type;
278
        }
279
280 21
        $allowsNull = $this->allowsNull();
281 21
        if (is_object($parameterType)) {
282 3
            $parameterType = $parameterType->toString();
283 21
        } elseif (is_string($parameterType)) {
284 17
            $isBuiltin = true;
285
        } else {
286 6
            return null;
287
        }
288
289 17
        return new ReflectionType($parameterType, $allowsNull, $isBuiltin);
290
    }
291
292
    /**
293
     * @inheritDoc
294
     */
295 4
    public function hasType()
296
    {
297 4
        $hasType = isset($this->parameterNode->type);
298
299 4
        return $hasType;
300
    }
301
302
    /**
303
     * @inheritDoc
304
     */
305 3
    public function isArray()
306
    {
307 3
        return 'array' === $this->parameterNode->type;
308
    }
309
310
    /**
311
     * @inheritDoc
312
     */
313 3
    public function isCallable()
314
    {
315 3
        return 'callable' === $this->parameterNode->type;
316
    }
317
318
    /**
319
     * @inheritDoc
320
     */
321 45
    public function isDefaultValueAvailable()
322
    {
323 45
        return isset($this->parameterNode->default);
324
    }
325
326
    /**
327
     * {@inheritDoc}
328
     */
329 5
    public function isDefaultValueConstant()
330
    {
331 5
        return $this->isDefaultValueConstant;
332
    }
333
334
    /**
335
     * {@inheritDoc}
336
     */
337 36
    public function isOptional()
338
    {
339 36
        return $this->isVariadic() || ($this->isDefaultValueAvailable() && $this->haveSiblingsDefalutValues());
340
    }
341
342
    /**
343
     * @inheritDoc
344
     */
345 20
    public function isPassedByReference()
346
    {
347 20
        return (bool) $this->parameterNode->byRef;
348
    }
349
350
    /**
351
     * @inheritDoc
352
     */
353 52
    public function isVariadic()
354
    {
355 52
        return (bool) $this->parameterNode->variadic;
356
    }
357
358
    /**
359
     * Returns if all following parameters have a default value definition.
360
     *
361
     * @return bool
362
     * @throws ReflectionException If could not fetch declaring function reflection
363
     */
364 14
    protected function haveSiblingsDefalutValues()
365
    {
366 14
        $function = $this->getDeclaringFunction();
367 14
        if (null === $function) {
368
            throw new ReflectionException('Could not get the declaring function reflection.');
369
        }
370
371
        /** @var \ReflectionParameter[] $remainingParameters */
372 14
        $remainingParameters = array_slice($function->getParameters(), $this->parameterIndex + 1);
373 14
        foreach ($remainingParameters as $reflectionParameter) {
374 14
            if (!$reflectionParameter->isDefaultValueAvailable()) {
375 14
                return false;
376
            }
377
        }
378
379 13
        return true;
380
    }
381
}
382