Completed
Push — master ( 6c5557...d84e1e )
by Alexander
03:46
created

ReflectionParameter   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 317
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 98.1%

Importance

Changes 2
Bugs 1 Features 1
Metric Value
wmc 46
c 2
b 1
f 1
lcom 1
cbo 7
dl 0
loc 317
ccs 103
cts 105
cp 0.981
rs 8.4

20 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 29 3
A __debugInfo() 0 6 1
C __toString() 0 29 13
A allowsNull() 0 9 3
A canBePassedByValue() 0 4 1
A getClass() 0 15 4
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 isArray() 0 4 1
A isCallable() 0 4 1
A isDefaultValueAvailable() 0 4 1
A isDefaultValueConstant() 0 4 1
A isOptional() 0 4 2
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\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
     * Name of the function or array pair [class name, method name]
56
     *
57
     * @var string|array
58
     */
59
    private $functionName;
60
61
    /**
62
     * Index of parameter in the list
63
     *
64
     * @var int
65
     */
66
    private $parameterIndex = 0;
67
68
    /**
69
     * Concrete parameter node
70
     *
71
     * @var Param
72
     */
73
    private $parameterNode;
74
75
    /**
76
     * Initializes a reflection for the property
77
     *
78
     * @param string|array $functionName Name of the function/method
79
     * @param string $parameterName Name of the parameter to reflect
80
     * @param Param $parameterNode Parameter definition node
81
     * @param int $parameterIndex Index of parameter
82
     * @param \ReflectionFunctionAbstract $declaringFunction
83
     */
84 9
    public function __construct(
85
        $functionName,
86
        $parameterName,
87
        Param $parameterNode = null,
88
        $parameterIndex = 0,
89
        \ReflectionFunctionAbstract $declaringFunction = null
90
    ) {
91 9
        $this->functionName      = $functionName;
92
        // Let's unset original read-only property to have a control over it via __get
93 9
        unset($this->name);
94
95 9
        $this->parameterNode     = $parameterNode;
96 9
        $this->parameterIndex    = $parameterIndex;
97 9
        $this->declaringFunction = $declaringFunction;
98
99 9
        if ($this->isDefaultValueAvailable()) {
100 8
            if ($declaringFunction instanceof \ReflectionMethod) {
101 2
                $context = $declaringFunction->getDeclaringClass();
102 2
            } else {
103 6
                $context = $declaringFunction;
104
            };
105
106 8
            $expressionSolver = new NodeExpressionResolver($context);
107 8
            $expressionSolver->process($this->parameterNode->default);
108 8
            $this->defaultValue             = $expressionSolver->getValue();
109 8
            $this->isDefaultValueConstant   = $expressionSolver->isConstant();
110 8
            $this->defaultValueConstantName = $expressionSolver->getConstantName();
111 8
        }
112 9
    }
113
114
    /**
115
     * Emulating original behaviour of reflection
116
     */
117 1
    public function __debugInfo()
118
    {
119
        return array(
120 1
            'name' => $this->parameterNode->name,
121 1
        );
122
    }
123
124
    /**
125
     * Returns string representation of this parameter.
126
     *
127
     * @return string
128
     */
129 2
    public function __toString()
130
    {
131 2
        $parameterType = $this->parameterNode->type;
132 2
        if (is_object($parameterType)) {
133 1
            $parameterType = $parameterType->toString();
134 1
        }
135 2
        $isNullableParam = !empty($parameterType) && $this->allowsNull();
136 2
        $isOptional      = $this->isOptional();
137 2
        $defaultValue    = '';
138 2
        if ($isOptional) {
139 2
            $defaultValue = $this->getDefaultValue();
140 2
            if (is_string($defaultValue) && strlen($defaultValue) > 15) {
141 1
                $defaultValue = substr($defaultValue, 0, 15) . '...';
142 1
            }
143 2
            $defaultValue = str_replace('\\\\', '\\', var_export($defaultValue, true));
144 2
        }
145
146 2
        return sprintf(
147 2
            'Parameter #%d [ %s %s%s%s%s$%s%s ]',
148 2
            $this->parameterIndex,
149 2
            ($this->isVariadic() || $isOptional) ? '<optional>' : '<required>',
150 2
            $parameterType ? ltrim($parameterType, '\\') . ' ' : '',
151 2
            $isNullableParam ? 'or NULL ' : '',
152 2
            $this->isVariadic() ? '...' : '',
153 2
            $this->isPassedByReference() ? '&' : '',
154 2
            $this->getName(),
155 2
            $isOptional ? (' = ' . $defaultValue) : ''
156 2
        );
157
    }
158
159
    /**
160
     * {@inheritDoc}
161
     */
162 1
    public function allowsNull()
163
    {
164 1
        $hasDefaultNull = $this->isDefaultValueAvailable() && $this->getDefaultValue() === null;
165 1
        if ($hasDefaultNull) {
166 1
            return true;
167
        }
168
169 1
        return !isset($this->parameterNode->type);
170
    }
171
172
    /**
173
     * {@inheritDoc}
174
     */
175 1
    public function canBePassedByValue()
176
    {
177 1
        return !$this->isPassedByReference();
178
    }
179
180
    /**
181
     * @inheritDoc
182
     */
183 1
    public function getClass()
184
    {
185 1
        $parameterType = $this->parameterNode->type;
186 1
        if ($parameterType instanceof Name) {
187 1
            if (!$parameterType instanceof Name\FullyQualified) {
188
                throw new ReflectionException("Can not resolve a class name for parameter");
189
            }
190 1
            $className   = $parameterType->toString();
191 1
            $classExists = class_exists($className, false);
192
193 1
            return $classExists ? new \ReflectionClass($className) : new ReflectionClass($className);
194
        }
195
196 1
        return null;
197
    }
198
199
    /**
200
     * {@inheritDoc}
201
     */
202 2
    public function getDeclaringClass()
203
    {
204 2
        if ($this->declaringFunction instanceof \ReflectionMethod) {
205 1
            return $this->declaringFunction->getDeclaringClass();
206
        };
207
208 1
        return null;
209
    }
210
211
    /**
212
     * {@inheritDoc}
213
     */
214 2
    public function getDeclaringFunction()
215
    {
216 2
        return $this->declaringFunction;
217
    }
218
219
    /**
220
     * {@inheritDoc}
221
     */
222 3
    public function getDefaultValue()
223
    {
224 3
        if (!$this->isDefaultValueAvailable()) {
225 1
            throw new ReflectionException('Internal error: Failed to retrieve the default value');
226
        }
227
228 2
        return $this->defaultValue;
229
    }
230
231
    /**
232
     * {@inheritDoc}
233
     */
234 2
    public function getDefaultValueConstantName()
235
    {
236 2
        if (!$this->isDefaultValueAvailable()) {
237 1
            throw new ReflectionException('Internal error: Failed to retrieve the default value');
238
        }
239
240 1
        return $this->defaultValueConstantName;
241
    }
242
243
    /**
244
     * @inheritDoc
245
     */
246 2
    public function getName()
247
    {
248 2
        return $this->parameterNode->name;
249
    }
250
251
    /**
252
     * {@inheritDoc}
253
     */
254 1
    public function getPosition()
255
    {
256 1
        return $this->parameterIndex;
257
    }
258
259
    /**
260
     * @inheritDoc
261
     */
262 1
    public function isArray()
263
    {
264 1
        return 'array' === $this->parameterNode->type;
265
    }
266
267
    /**
268
     * @inheritDoc
269
     */
270 1
    public function isCallable()
271
    {
272 1
        return 'callable' === $this->parameterNode->type;
273
    }
274
275
    /**
276
     * @inheritDoc
277
     */
278 9
    public function isDefaultValueAvailable()
279
    {
280 9
        return isset($this->parameterNode->default);
281
    }
282
283
    /**
284
     * {@inheritDoc}
285
     */
286 1
    public function isDefaultValueConstant()
287
    {
288 1
        return $this->isDefaultValueConstant;
289
    }
290
291
    /**
292
     * {@inheritDoc}
293
     */
294 3
    public function isOptional()
295
    {
296 3
        return $this->isDefaultValueAvailable() && $this->haveSiblingsDefalutValues();
297
    }
298
299
    /**
300
     * @inheritDoc
301
     */
302 2
    public function isPassedByReference()
303
    {
304 2
        return (bool) $this->parameterNode->byRef;
305
    }
306
307
    /**
308
     * @inheritDoc
309
     */
310 2
    public function isVariadic()
311
    {
312 2
        return (bool) $this->parameterNode->variadic;
313
    }
314
315
    /**
316
     * Returns if all following parameters have a default value definition.
317
     *
318
     * @return bool
319
     * @throws ReflectionException If could not fetch declaring function reflection
320
     */
321 2
    protected function haveSiblingsDefalutValues()
322
    {
323 2
        $function = $this->getDeclaringFunction();
324 2
        if (null === $function) {
325
            throw new ReflectionException('Could not get the declaring function reflection.');
326
        }
327
328
        /** @var \ReflectionParameter[] $remainingParameters */
329 2
        $remainingParameters = array_slice($function->getParameters(), $this->parameterIndex + 1);
330 2
        foreach ($remainingParameters as $reflectionParameter) {
331 2
            if (!$reflectionParameter->isDefaultValueAvailable()) {
332 1
                return false;
333
            }
334 2
        }
335
336 2
        return true;
337
    }
338
}
339