Completed
Push — master ( c1cd20...63afd1 )
by Alexander
02:14
created

ReflectionParameter::isVariadic()   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 9
    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 9
        unset($this->name);
86
87 9
        $this->parameterNode     = $parameterNode;
88 9
        $this->parameterIndex    = $parameterIndex;
89 9
        $this->declaringFunction = $declaringFunction;
90
91 9
        if ($this->isDefaultValueAvailable()) {
92 9
            if ($declaringFunction instanceof \ReflectionMethod) {
93 2
                $context = $declaringFunction->getDeclaringClass();
94 2
            } else {
95 7
                $context = $declaringFunction;
96
            };
97
98 9
            $expressionSolver = new NodeExpressionResolver($context);
99 9
            $expressionSolver->process($this->parameterNode->default);
100 9
            $this->defaultValue             = $expressionSolver->getValue();
101 9
            $this->isDefaultValueConstant   = $expressionSolver->isConstant();
102 9
            $this->defaultValueConstantName = $expressionSolver->getConstantName();
103 9
        }
104 9
    }
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 1
        );
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 1
        }
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 1
            }
135 3
            $defaultValue = str_replace('\\\\', '\\', var_export($defaultValue, true));
136 3
        }
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 3
        );
149
    }
150
151
    /**
152
     * {@inheritDoc}
153
     */
154 1
    public function allowsNull()
155
    {
156 1
        $hasDefaultNull = $this->isDefaultValueAvailable() && $this->getDefaultValue() === null;
157 1
        if ($hasDefaultNull) {
158 1
            return true;
159
        }
160
161 1
        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 4
    public function getDefaultValue()
215
    {
216 4
        if (!$this->isDefaultValueAvailable()) {
217 1
            throw new ReflectionException('Internal error: Failed to retrieve the default value');
218
        }
219
220 3
        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 3
    public function getName()
239
    {
240 3
        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 isArray()
255
    {
256 1
        return 'array' === $this->parameterNode->type;
257
    }
258
259
    /**
260
     * @inheritDoc
261
     */
262 1
    public function isCallable()
263
    {
264 1
        return 'callable' === $this->parameterNode->type;
265
    }
266
267
    /**
268
     * @inheritDoc
269
     */
270 9
    public function isDefaultValueAvailable()
271
    {
272 9
        return isset($this->parameterNode->default);
273
    }
274
275
    /**
276
     * {@inheritDoc}
277
     */
278 1
    public function isDefaultValueConstant()
279
    {
280 1
        return $this->isDefaultValueConstant;
281
    }
282
283
    /**
284
     * {@inheritDoc}
285
     */
286 3
    public function isOptional()
287
    {
288 3
        return $this->isDefaultValueAvailable() && $this->haveSiblingsDefalutValues();
289
    }
290
291
    /**
292
     * @inheritDoc
293
     */
294 3
    public function isPassedByReference()
295
    {
296 3
        return (bool) $this->parameterNode->byRef;
297
    }
298
299
    /**
300
     * @inheritDoc
301
     */
302 3
    public function isVariadic()
303
    {
304 3
        return (bool) $this->parameterNode->variadic;
305
    }
306
307
    /**
308
     * Returns if all following parameters have a default value definition.
309
     *
310
     * @return bool
311
     * @throws ReflectionException If could not fetch declaring function reflection
312
     */
313 3
    protected function haveSiblingsDefalutValues()
314
    {
315 3
        $function = $this->getDeclaringFunction();
316 3
        if (null === $function) {
317
            throw new ReflectionException('Could not get the declaring function reflection.');
318
        }
319
320
        /** @var \ReflectionParameter[] $remainingParameters */
321 3
        $remainingParameters = array_slice($function->getParameters(), $this->parameterIndex + 1);
322 3
        foreach ($remainingParameters as $reflectionParameter) {
323 3
            if (!$reflectionParameter->isDefaultValueAvailable()) {
324 1
                return false;
325
            }
326 3
        }
327
328 3
        return true;
329
    }
330
}
331