Completed
Push — master ( d36faf...a71d04 )
by Alexander
22s
created

ReflectionParameter::canBePassedByValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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