Completed
Push — master ( adc7b4...55ea92 )
by Alexander
04:12 queued 01:10
created

ReflectionParameter::getPosition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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