Completed
Push — master ( 79fae7...f16f1c )
by Alexander
07:00
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
Metric Value
dl 0
loc 4
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 10
    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 10
        unset($this->name);
86
87 10
        $this->parameterNode     = $parameterNode;
88 10
        $this->parameterIndex    = $parameterIndex;
89 10
        $this->declaringFunction = $declaringFunction;
90
91 10
        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 10
    }
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 3
            $defaultValue = $this->getDefaultValue();
132 3
            if (is_string($defaultValue) && strlen($defaultValue) > 15) {
133 1
                $defaultValue = substr($defaultValue, 0, 15) . '...';
134
            }
135 3
            $defaultValue = str_replace('\\\\', '\\', var_export($defaultValue, true));
136
        }
137
138 3
        return sprintf(
139 3
            'Parameter #%d [ %s %s%s%s%s$%s%s ]',
140 3
            $this->parameterIndex,
141 3
            ($this->isVariadic() || $isOptional) ? '<optional>' : '<required>',
142 3
            $parameterType ? ltrim($parameterType, '\\') . ' ' : '',
143 3
            $isNullableParam ? 'or NULL ' : '',
144 3
            $this->isVariadic() ? '...' : '',
145 3
            $this->isPassedByReference() ? '&' : '',
146 3
            $this->getName(),
147 3
            $isOptional ? (' = ' . $defaultValue) : ''
148
        );
149
    }
150
151
    /**
152
     * {@inheritDoc}
153
     */
154 2
    public function allowsNull()
155
    {
156 2
        $hasDefaultNull = $this->isDefaultValueAvailable() && $this->getDefaultValue() === null;
157 2
        if ($hasDefaultNull) {
158 2
            return true;
159
        }
160
161 2
        return !isset($this->parameterNode->type);
162
    }
163
164
    /**
165
     * {@inheritDoc}
166
     */
167 1
    public function canBePassedByValue()
168
    {
169 1
        return !$this->isPassedByReference();
170
    }
171
172
    /**
173
     * @inheritDoc
174
     */
175 1
    public function getClass()
176
    {
177 1
        $parameterType = $this->parameterNode->type;
178 1
        if ($parameterType instanceof Name) {
179 1
            if (!$parameterType instanceof Name\FullyQualified) {
180
                throw new ReflectionException("Can not resolve a class name for parameter");
181
            }
182 1
            $className   = $parameterType->toString();
183 1
            $classExists = class_exists($className, false);
184
185 1
            return $classExists ? new \ReflectionClass($className) : new ReflectionClass($className);
186
        }
187
188 1
        return null;
189
    }
190
191
    /**
192
     * {@inheritDoc}
193
     */
194 2
    public function getDeclaringClass()
195
    {
196 2
        if ($this->declaringFunction instanceof \ReflectionMethod) {
197 1
            return $this->declaringFunction->getDeclaringClass();
198
        };
199
200 1
        return null;
201
    }
202
203
    /**
204
     * {@inheritDoc}
205
     */
206 3
    public function getDeclaringFunction()
207
    {
208 3
        return $this->declaringFunction;
209
    }
210
211
    /**
212
     * {@inheritDoc}
213
     */
214 5
    public function getDefaultValue()
215
    {
216 5
        if (!$this->isDefaultValueAvailable()) {
217 1
            throw new ReflectionException('Internal error: Failed to retrieve the default value');
218
        }
219
220 4
        return $this->defaultValue;
221
    }
222
223
    /**
224
     * {@inheritDoc}
225
     */
226 2
    public function getDefaultValueConstantName()
227
    {
228 2
        if (!$this->isDefaultValueAvailable()) {
229 1
            throw new ReflectionException('Internal error: Failed to retrieve the default value');
230
        }
231
232 1
        return $this->defaultValueConstantName;
233
    }
234
235
    /**
236
     * @inheritDoc
237
     */
238 4
    public function getName()
239
    {
240 4
        return $this->parameterNode->name;
241
    }
242
243
    /**
244
     * {@inheritDoc}
245
     */
246 1
    public function getPosition()
247
    {
248 1
        return $this->parameterIndex;
249
    }
250
251
    /**
252
     * @inheritDoc
253
     */
254 1
    public function getType()
255
    {
256 1
        $isBuiltin     = false;
257 1
        $allowsNull    = $this->allowsNull();
258 1
        $parameterType = $this->parameterNode->type;
259 1
        if (is_object($parameterType)) {
260 1
            $parameterType = $parameterType->toString();
261 1
        } elseif (is_string($parameterType)) {
262 1
            $isBuiltin = true;
263
        }
264
265 1
        return new ReflectionType($parameterType, $allowsNull, $isBuiltin);
266
    }
267
268
    /**
269
     * @inheritDoc
270
     */
271 2
    public function hasType()
272
    {
273 2
        $hasType = isset($this->parameterNode->type);
274
275 2
        return $hasType;
276
    }
277
278
    /**
279
     * @inheritDoc
280
     */
281 1
    public function isArray()
282
    {
283 1
        return 'array' === $this->parameterNode->type;
284
    }
285
286
    /**
287
     * @inheritDoc
288
     */
289 1
    public function isCallable()
290
    {
291 1
        return 'callable' === $this->parameterNode->type;
292
    }
293
294
    /**
295
     * @inheritDoc
296
     */
297 10
    public function isDefaultValueAvailable()
298
    {
299 10
        return isset($this->parameterNode->default);
300
    }
301
302
    /**
303
     * {@inheritDoc}
304
     */
305 1
    public function isDefaultValueConstant()
306
    {
307 1
        return $this->isDefaultValueConstant;
308
    }
309
310
    /**
311
     * {@inheritDoc}
312
     */
313 3
    public function isOptional()
314
    {
315 3
        return $this->isDefaultValueAvailable() && $this->haveSiblingsDefalutValues();
316
    }
317
318
    /**
319
     * @inheritDoc
320
     */
321 3
    public function isPassedByReference()
322
    {
323 3
        return (bool) $this->parameterNode->byRef;
324
    }
325
326
    /**
327
     * @inheritDoc
328
     */
329 3
    public function isVariadic()
330
    {
331 3
        return (bool) $this->parameterNode->variadic;
332
    }
333
334
    /**
335
     * Returns if all following parameters have a default value definition.
336
     *
337
     * @return bool
338
     * @throws ReflectionException If could not fetch declaring function reflection
339
     */
340 3
    protected function haveSiblingsDefalutValues()
341
    {
342 3
        $function = $this->getDeclaringFunction();
343 3
        if (null === $function) {
344
            throw new ReflectionException('Could not get the declaring function reflection.');
345
        }
346
347
        /** @var \ReflectionParameter[] $remainingParameters */
348 3
        $remainingParameters = array_slice($function->getParameters(), $this->parameterIndex + 1);
349 3
        foreach ($remainingParameters as $reflectionParameter) {
350 3
            if (!$reflectionParameter->isDefaultValueAvailable()) {
351 3
                return false;
352
            }
353
        }
354
355 3
        return true;
356
    }
357
}
358