Completed
Pull Request — master (#21)
by Alexander
03:05
created

ReflectionParameter::isDefaultValueConstant()   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 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 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 1
    public function getClass()
180
    {
181 1
        $parameterType = $this->parameterNode->type;
182 1
        if ($parameterType instanceof Name) {
183 1
            if (!$parameterType instanceof Name\FullyQualified) {
184
                throw new ReflectionException("Can not resolve a class name for parameter");
185
            }
186 1
            $className   = $parameterType->toString();
187 1
            $classOrInterfaceExists = class_exists($className, false) || interface_exists($className, false);
188
189 1
            return $classOrInterfaceExists ? new \ReflectionClass($className) : new ReflectionClass($className);
190
        }
191
192 1
        return null;
193
    }
194
195
    /**
196
     * {@inheritDoc}
197
     */
198 2
    public function getDeclaringClass()
199
    {
200 2
        if ($this->declaringFunction instanceof \ReflectionMethod) {
201 1
            return $this->declaringFunction->getDeclaringClass();
202
        };
203
204 1
        return null;
205
    }
206
207
    /**
208
     * {@inheritDoc}
209
     */
210 3
    public function getDeclaringFunction()
211
    {
212 3
        return $this->declaringFunction;
213
    }
214
215
    /**
216
     * {@inheritDoc}
217
     */
218 5
    public function getDefaultValue()
219
    {
220 5
        if (!$this->isDefaultValueAvailable()) {
221 1
            throw new ReflectionException('Internal error: Failed to retrieve the default value');
222
        }
223
224 4
        return $this->defaultValue;
225
    }
226
227
    /**
228
     * {@inheritDoc}
229
     */
230 2
    public function getDefaultValueConstantName()
231
    {
232 2
        if (!$this->isDefaultValueAvailable()) {
233 1
            throw new ReflectionException('Internal error: Failed to retrieve the default value');
234
        }
235
236 1
        return $this->defaultValueConstantName;
237
    }
238
239
    /**
240
     * @inheritDoc
241
     */
242 4
    public function getName()
243
    {
244 4
        return $this->parameterNode->name;
245
    }
246
247
    /**
248
     * {@inheritDoc}
249
     */
250 1
    public function getPosition()
251
    {
252 1
        return $this->parameterIndex;
253
    }
254
255
    /**
256
     * @inheritDoc
257
     */
258 1
    public function getType()
259
    {
260 1
        $isBuiltin     = false;
261 1
        $allowsNull    = $this->allowsNull();
262 1
        $parameterType = $this->parameterNode->type;
263 1
        if (is_object($parameterType)) {
264 1
            $parameterType = $parameterType->toString();
265 1
        } elseif (is_string($parameterType)) {
266 1
            $isBuiltin = true;
267
        } else {
268 1
            return null;
269
        }
270
271 1
        return new ReflectionType($parameterType, $allowsNull, $isBuiltin);
272
    }
273
274
    /**
275
     * @inheritDoc
276
     */
277 2
    public function hasType()
278
    {
279 2
        $hasType = isset($this->parameterNode->type);
280
281 2
        return $hasType;
282
    }
283
284
    /**
285
     * @inheritDoc
286
     */
287 1
    public function isArray()
288
    {
289 1
        return 'array' === $this->parameterNode->type;
290
    }
291
292
    /**
293
     * @inheritDoc
294
     */
295 1
    public function isCallable()
296
    {
297 1
        return 'callable' === $this->parameterNode->type;
298
    }
299
300
    /**
301
     * @inheritDoc
302
     */
303 10
    public function isDefaultValueAvailable()
304
    {
305 10
        return isset($this->parameterNode->default);
306
    }
307
308
    /**
309
     * {@inheritDoc}
310
     */
311 1
    public function isDefaultValueConstant()
312
    {
313 1
        return $this->isDefaultValueConstant;
314
    }
315
316
    /**
317
     * {@inheritDoc}
318
     */
319 3
    public function isOptional()
320
    {
321 3
        return $this->isDefaultValueAvailable() && $this->haveSiblingsDefalutValues();
322
    }
323
324
    /**
325
     * @inheritDoc
326
     */
327 3
    public function isPassedByReference()
328
    {
329 3
        return (bool) $this->parameterNode->byRef;
330
    }
331
332
    /**
333
     * @inheritDoc
334
     */
335 3
    public function isVariadic()
336
    {
337 3
        return (bool) $this->parameterNode->variadic;
338
    }
339
340
    /**
341
     * Returns if all following parameters have a default value definition.
342
     *
343
     * @return bool
344
     * @throws ReflectionException If could not fetch declaring function reflection
345
     */
346 3
    protected function haveSiblingsDefalutValues()
347
    {
348 3
        $function = $this->getDeclaringFunction();
349 3
        if (null === $function) {
350
            throw new ReflectionException('Could not get the declaring function reflection.');
351
        }
352
353
        /** @var \ReflectionParameter[] $remainingParameters */
354 3
        $remainingParameters = array_slice($function->getParameters(), $this->parameterIndex + 1);
355 3
        foreach ($remainingParameters as $reflectionParameter) {
356 3
            if (!$reflectionParameter->isDefaultValueAvailable()) {
357 3
                return false;
358
            }
359
        }
360
361 2
        return true;
362
    }
363
}
364