Passed
Push — PSR-11-2 ( e86d47...dfb1a3 )
by Nikolaos
05:09
created

Resolver   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 386
Duplicated Lines 0 %

Test Coverage

Coverage 78.26%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 46
eloc 135
c 1
b 0
f 0
dl 0
loc 386
ccs 108
cts 138
cp 0.7826
rs 8.72

13 Methods

Rating   Name   Duplication   Size   Complexity  
A getUnifiedParameters() 0 14 2
A setters() 0 3 1
A __construct() 0 8 1
A parameters() 0 3 1
A getUnified() 0 28 3
A getUnifiedMutations() 0 29 6
A getUnifiedSetters() 0 28 6
A resolve() 0 49 5
C getUnifiedParameter() 0 48 13
A unified() 0 3 1
A values() 0 3 1
A mutations() 0 3 1
A expandParameters() 0 21 5

How to fix   Complexity   

Complex Class

Complex classes like Resolver 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.

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 Resolver, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file is part of the Phalcon Framework.
5
 *
6
 * (c) Phalcon Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 *
11
 * Implementation of this file has been influenced by AuraPHP
12
 *
13
 * @link    https://github.com/auraphp/Aura.Di
14
 * @license https://github.com/auraphp/Aura.Di/blob/4.x/LICENSE
15
 */
16
17
declare(strict_types=1);
18
19
namespace Phalcon\Container\Resolver;
20
21
use ReflectionException;
22
use ReflectionParameter;
23
24
use function array_merge;
25
26
/**
27
 * Resolves class creation specifics based on constructor params and setter
28
 * definitions, unified across class defaults, inheritance hierarchies, and
29
 * configuration.
30
 *
31
 * @property ValueObject    $mutations
32
 * @property ValueObject    $parameters
33
 * @property Reflector      $reflector
34
 * @property ValueObject    $setters
35
 * @property ValueObject    $values
36
 * @property ValueObject    $unified
37
 */
38
class Resolver
39
{
40
    /**
41
     * Setter definitions in the form of `$mutations[$class][] = $value`.
42
     *
43
     * @var ValueObject
44
     */
45
    private $mutations;
46
47
    /**
48
     * Constructor params in the form `$params[$class][$name] = $value`.
49
     *
50
     * @var ValueObject
51
     */
52
    private $parameters;
53
54
    /**
55
     * A Reflector.
56
     *
57
     * @var Reflector
58
     */
59
    private $reflector;
60
61
    /**
62
     * Setter definitions in the form of `$setters[$class][$method] = $value`.
63
     *
64
     * @var ValueObject
65
     */
66
    private $setters;
67
68
    /**
69
     * Constructor params and setter definitions, unified across class
70
     * defaults, inheritance hierarchies, and configuration.
71
     *
72
     * @var ValueObject
73
     */
74
    private $unified;
75
76
    /**
77
     * Arbitrary values in the form of `$values[$key] = $value`.
78
     *
79
     * @var ValueObject
80
     */
81
    private $values;
82
83
    /**
84
     *
85
     * Constructor.
86
     *
87
     * @param Reflector $reflector A collection point for Reflection data.
88
     *
89
     */
90 67
    public function __construct(Reflector $reflector)
91
    {
92 67
        $this->reflector  = $reflector;
93 67
        $this->mutations  = new ValueObject();
94 67
        $this->parameters = new ValueObject();
95 67
        $this->setters    = new ValueObject();
96 67
        $this->unified    = new ValueObject();
97 67
        $this->values     = new ValueObject();
98 67
    }
99
100
    /**
101
     * Returns the unified constructor params and setters for a class.
102
     *
103
     * @param string $class The class name to return values for.
104
     *
105
     * @return Blueprint A blueprint how to construct an object
106
     * @throws ReflectionException
107
     */
108 37
    public function getUnified(string $class): Blueprint
109
    {
110
        // have values already been unified for this class?
111 37
        if ($this->unified->has($class)) {
112 2
            return $this->unified->get($class);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->unified->get($class) could return the type null which is incompatible with the type-hinted return Phalcon\Container\Resolver\Blueprint. Consider adding an additional type-check to rule them out.
Loading history...
113
        }
114
115
        // fetch the values for parents so we can inherit them
116 37
        $parent = get_parent_class($class);
117 37
        if ($parent) {
118 9
            $spec = $this->getUnified($parent);
119
        } else {
120 37
            $spec = new Blueprint($class);
121
        }
122
123
        // stores the unified params and setters
124 37
        $this->unified->set(
125 37
            $class,
126 37
            new Blueprint(
127 37
                $class,
128 37
                $this->getUnifiedParameters($class, $spec->getParameters()),
129 36
                $this->getUnifiedSetters($class, $spec->getSetters()),
130 36
                $this->getUnifiedMutations($class, $spec->getMutations())
131
            )
132
        );
133
134
        // done, return the unified values
135 36
        return $this->unified->get($class);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->unified->get($class) could return the type null which is incompatible with the type-hinted return Phalcon\Container\Resolver\Blueprint. Consider adding an additional type-check to rule them out.
Loading history...
136
    }
137
138 2
    public function mutations(): ValueObject
139
    {
140 2
        return $this->mutations;
141
    }
142
143 8
    public function parameters(): ValueObject
144
    {
145 8
        return $this->parameters;
146
    }
147
148
    /**
149
     * Creates and returns a new instance of a class using reflection and
150
     * the configuration parameters, optionally with overrides, invoking Lazy
151
     * values along the way.
152
     *
153
     * @param Blueprint $blueprint The blueprint to be resolved containing
154
     *                             its overrides for this specific case.
155
     * @param array     $contextualBlueprints
156
     *
157
     * @return object
158
     * @throws ReflectionException
159
     */
160 25
    public function resolve(Blueprint $blueprint, array $contextualBlueprints = []): object
161
    {
162 25
        if ($contextualBlueprints === []) {
163 25
            return call_user_func(
164 25
                $this->expandParameters(
165
                    $this
166 25
                        ->getUnified($blueprint->getClassName())
167 24
                        ->merge($blueprint)
168
                ),
169 24
                $this->reflector->getClass($blueprint->getClassName())
170
            );
171
        }
172
173
        $remember = new self($this->reflector);
174
175
        foreach ($contextualBlueprints as $contextualBlueprint) {
176
            $className = $contextualBlueprint->getClassName();
177
178
            $remember->parameters->set($className, $this->parameters->get($className, []));
179
            $remember->setters->set($className, $this->setters->get($className, []));
180
            $remember->mutations->set($className, $this->mutations->get($className, []));
181
182
            $this->parameters->merge($className, $contextualBlueprint->getParams());
183
            $this->setters->merge($className, $contextualBlueprint->getSetters());
184
            $this->mutations->merge($className, $contextualBlueprint->getMutations());
185
186
            $this->unified->remove($className);
187
        }
188
189
        $resolved = call_user_func(
190
            $this->expandParameters($this->getUnified($blueprint->getClassName())->merge($blueprint)),
191
            $this->reflector->getClass($blueprint->getClassName())
192
        );
193
194
        foreach ($contextualBlueprints as $contextualBlueprint) {
195
            $className = $contextualBlueprint->getClassName();
196
197
            $this->parameters->set($className, $remember->parameters->get($className, []));
198
            $this->setters->set($className, $remember->setters->get($className, []));
199
            $this->mutations->set($className, $remember->mutations->get($className, []));
200
201
            if ($remember->unified->has($className)) {
202
                $this->unified->set($className, $remember->unified->get($className));
203
            } else {
204
                $this->unified->remove($className);
205
            }
206
        }
207
208
        return $resolved;
209
    }
210
211 9
    public function setters(): ValueObject
212
    {
213 9
        return $this->setters;
214
    }
215
216 1
    public function unified(): ValueObject
217
    {
218 1
        return $this->unified;
219
    }
220
221 5
    public function values(): ValueObject
222
    {
223 5
        return $this->values;
224
    }
225
226
    /**
227
     * Returns the unified mutations for a class.
228
     *
229
     * Class-specific mutations are executed last before trait-based mutations
230
     * and before interface-based mutations.
231
     *
232
     * @param string $class  The class name to return values for.
233
     * @param array  $parent The parent unified setters.
234
     *
235
     * @return array The unified mutations.
236
     */
237 36
    protected function getUnifiedMutations(string $class, array $parent): array
238
    {
239 36
        $unified = $parent;
240
241
        // look for interface mutations
242 36
        $interfaces = class_implements($class);
243 36
        foreach ($interfaces as $interface) {
244
            if ($this->mutations->has($interface)) {
245
                $unified = $this->mutations->merge($interface, $unified);
246
            }
247
        }
248
249
        // look for trait mutations
250 36
        $traits = $this->reflector->getTraits($class);
251 36
        foreach ($traits as $trait) {
252 5
            if ($this->mutations->has($trait)) {
253
                $unified = $this->mutations->merge($trait, $unified);
254
            }
255
        }
256
257
        // look for class mutations
258 36
        if ($this->mutations->has($class)) {
259
            $unified = array_merge(
260
                $unified,
261
                $this->mutations->get($class)
262
            );
263
        }
264
265 36
        return $unified;
266
    }
267
268
    /**
269
     * Returns a unified param.
270
     *
271
     * @param ReflectionParameter $rparam A parameter reflection.
272
     * @param string              $class  The class name to return values for.
273
     * @param array               $parent The parent unified params.
274
     *
275
     * @return mixed|DefaultValueParameter|UnresolvedParameter
276
     * @throws ReflectionException
277
     */
278 27
    protected function getUnifiedParameter(ReflectionParameter $rparam, string $class, array $parent)
279
    {
280 27
        $name     = $rparam->getName();
281 27
        $position = $rparam->getPosition();
282
283
        // is there a positional value explicitly from the current class?
284 27
        $explicitPosition = $this->parameters->has($class)
285 27
            && array_key_exists($position, $this->parameters->get($class))
0 ignored issues
show
Bug introduced by
It seems like $this->parameters->get($class) can also be of type null; however, parameter $search of array_key_exists() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

285
            && array_key_exists($position, /** @scrutinizer ignore-type */ $this->parameters->get($class))
Loading history...
286 27
            && !$this->parameters->get($class)[$position] instanceof UnresolvedParameter;
287
288 27
        if ($explicitPosition) {
289 1
            return $this->parameters->get($class)[$position];
290
        }
291
292
        // is there a named value explicitly from the current class?
293 27
        $explicitNamed = $this->parameters->has($class)
294 27
            && array_key_exists($name, $this->parameters->get($class))
295 27
            && !$this->parameters->get($class)[$name] instanceof UnresolvedParameter;
296
297 27
        if ($explicitNamed) {
298 5
            return $this->parameters->get($class)[$name];
299
        }
300
301
        // is there a named value implicitly inherited from the parent class?
302
        // (there cannot be a positional parent. this is because the unified
303
        // values are stored by name, not position.)
304 23
        $implicitNamed = array_key_exists($name, $parent)
305 23
            && !$parent[$name] instanceof UnresolvedParameter
306 23
            && !$parent[$name] instanceof DefaultValueParameter;
307
308 23
        if ($implicitNamed) {
309 2
            return $parent[$name];
310
        }
311
312
        // is a default value available for the current class?
313 22
        if ($rparam->isDefaultValueAvailable()) {
314 16
            return new DefaultValueParameter($name, $rparam->getDefaultValue());
315
        }
316
317
        // is a default value available for the parent class?
318 13
        $parentDefault = array_key_exists($name, $parent)
319 13
            && $parent[$name] instanceof DefaultValueParameter;
320 13
        if ($parentDefault) {
321 6
            return $parent[$name];
322
        }
323
324
        // param is missing
325 8
        return new UnresolvedParameter($name);
326
    }
327
328
    /**
329
     * Returns the unified constructor params for a class.
330
     *
331
     * @param string $class  The class name to return values for.
332
     * @param array  $parent The parent unified params.
333
     *
334
     * @return array The unified params.
335
     * @throws ReflectionException
336
     */
337 37
    protected function getUnifiedParameters(string $class, array $parent): array
338
    {
339
        // reflect on what params to pass, in which order
340 37
        $unified = [];
341 37
        $rparams = $this->reflector->getParameters($class);
342 36
        foreach ($rparams as $rparam) {
343 27
            $unified[$rparam->name] = $this->getUnifiedParameter(
344 27
                $rparam,
345
                $class,
346
                $parent
347
            );
348
        }
349
350 36
        return $unified;
351
    }
352
353
    /**
354
     * Returns the unified setters for a class.
355
     *
356
     * Class-specific setters take precendence over trait-based setters, which
357
     * take precedence over interface-based setters.
358
     *
359
     * @param string $class  The class name to return values for.
360
     * @param array  $parent The parent unified setters.
361
     *
362
     * @return array The unified setters.
363
     */
364 36
    protected function getUnifiedSetters(string $class, array $parent): array
365
    {
366 36
        $unified = $parent;
367
        // look for interface setters
368 36
        $interfaces = class_implements($class);
369 36
        foreach ($interfaces as $interface) {
370
            if ($this->setters->has($interface)) {
371
                $unified = $this->setters->merge($interface, $unified);
372
            }
373
        }
374
375
        // look for trait setters
376 36
        $traits = $this->reflector->getTraits($class);
377 36
        foreach ($traits as $trait) {
378 5
            if ($this->setters->has($trait)) {
379 5
                $unified = $this->setters->merge($trait, $unified);
380
            }
381
        }
382
383
        // look for class setters
384 36
        if ($this->setters->has($class)) {
385 3
            $unified = array_merge(
386 3
                $unified,
387 3
                $this->setters->get($class)
388
            );
389
        }
390
391 36
        return $unified;
392
    }
393
394
    /**
395
     * Expands variadic parameters onto the end of a constructor parameters
396
     * array.
397
     *
398
     * @param Blueprint $blueprint The blueprint to expand parameters for.
399
     *
400
     * @return Blueprint The blueprint with expanded constructor parameters.
401
     * @throws ReflectionException
402
     */
403 24
    protected function expandParameters(Blueprint $blueprint): Blueprint
404
    {
405 24
        $class  = $blueprint->getClassName();
406 24
        $params = $blueprint->getParameters();
407
408 24
        $variadicParams = [];
409 24
        foreach ($this->reflector->getParameters($class) as $reflectParam) {
410 20
            $paramName = $reflectParam->getName();
411 20
            if ($reflectParam->isVariadic() && is_array($params[$paramName])) {
412 2
                $variadicParams = array_merge($variadicParams, $params[$paramName]);
413 2
                unset($params[$paramName]);
414 2
                break; // There can only be one
415
            }
416
417 20
            if ($params[$paramName] instanceof DefaultValueParameter) {
418 6
                $params[$paramName] = $params[$paramName]->getValue();
419
            }
420
        }
421
422 24
        return $blueprint->replaceParameters(
423 24
            array_merge($params, array_values($variadicParams))
424
        );
425
    }
426
}
427