Completed
Push — master ( 34991a...adfc38 )
by Alexander
13s
created

ReflectionParameter   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 368
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 96.64%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 56
lcom 1
cbo 9
dl 0
loc 368
ccs 115
cts 119
cp 0.9664
rs 6.5957
c 1
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 28 3
A getNode() 0 4 1
A ___debugInfo() 0 6 1
C __toString() 0 30 12
A allowsNull() 0 14 4
A canBePassedByValue() 0 4 1
C getClass() 0 25 7
A getDeclaringClass() 0 8 2
A getDeclaringFunction() 0 4 1
A getDefaultValue() 0 8 2
A getDefaultValueConstantName() 0 8 2
A getName() 0 4 1
A getPosition() 0 4 1
A getType() 0 19 4
A hasType() 0 6 1
A isArray() 0 4 1
A isCallable() 0 4 1
A isDefaultValueAvailable() 0 4 1
A isDefaultValueConstant() 0 4 1
A isOptional() 0 4 3
A isPassedByReference() 0 4 1
A isVariadic() 0 4 1
A haveSiblingsDefalutValues() 0 17 4

How to fix   Complexity   

Complex Class

Complex classes like ReflectionParameter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ReflectionParameter, and based on these observations, apply Extract Interface, too.

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