Completed
Push — master ( eae51e...9e604e )
by Alexander
02:23
created

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