Completed
Push — PSR-11-2 ( a5ad88...7f5041 )
by Nikolaos
03:59
created

Resolver::getUnifiedSetters()   A

Complexity

Conditions 6
Paths 18

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 14
nc 18
nop 2
dl 0
loc 28
rs 9.2222
c 1
b 0
f 0
ccs 14
cts 14
cp 1
crap 6
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 Phalcon\Container\Exception\NoSuchProperty;
22
use ReflectionException;
23
use ReflectionParameter;
24
25
use function array_merge;
26
27
/**
28
 * Resolves class creation specifics based on constructor params and setter
29
 * definitions, unified across class defaults, inheritance hierarchies, and
30
 * configuration.
31
 *
32
 * @property ValueObject $mutations
33
 * @property ValueObject $parameters
34
 * @property Reflector   $reflector
35
 * @property ValueObject $setters
36
 * @property ValueObject $values
37
 * @property ValueObject $unified
38
 */
39
class Resolver
40
{
41
    /**
42
     * Setter definitions in the form of `$mutations[$class][] = $value`.
43
     *
44
     * @var ValueObject
45
     */
46
    private ValueObject $mutations;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_STRING, expecting T_FUNCTION or T_CONST
Loading history...
47
48
    /**
49
     * Constructor params in the form `$params[$class][$name] = $value`.
50
     *
51
     * @var ValueObject
52
     */
53
    private ValueObject $parameters;
54
55
    /**
56
     * A Reflector.
57
     *
58
     * @var Reflector
59
     */
60
    private Reflector $reflector;
61
62
    /**
63
     * Setter definitions in the form of `$setters[$class][$method] = $value`.
64
     *
65
     * @var ValueObject
66
     */
67
    private ValueObject $setters;
68
69
    /**
70
     * Constructor params and setter definitions, unified across class
71
     * defaults, inheritance hierarchies, and configuration.
72
     *
73
     * @var ValueObject
74
     */
75
    private ValueObject $unified;
76
77
    /**
78
     * Arbitrary values in the form of `$values[$key] = $value`.
79
     *
80
     * @var ValueObject
81
     */
82
    private ValueObject $values;
83
84
    /**
85
     *
86
     * Constructor.
87
     *
88
     * @param Reflector $reflector A collection point for Reflection data.
89
     *
90
     */
91 95
    public function __construct(Reflector $reflector)
92
    {
93 95
        $this->reflector  = $reflector;
94 95
        $this->mutations  = new ValueObject();
95 95
        $this->parameters = new ValueObject();
96 95
        $this->setters    = new ValueObject();
97 95
        $this->unified    = new ValueObject();
98 95
        $this->values     = new ValueObject();
99 95
    }
100
101
    /**
102
     * Returns the unified constructor params and setters for a class.
103
     *
104
     * @param string $class The class name to return values for.
105
     *
106
     * @return Blueprint A blueprint how to construct an object
107
     * @throws NoSuchProperty
108
     * @throws ReflectionException
109
     */
110 59
    public function getUnified(string $class): Blueprint
111
    {
112
        // have values already been unified for this class?
113 59
        if ($this->unified->has($class)) {
114 7
            return $this->unified->get($class);
115
        }
116
117
        // fetch the values for parents so we can inherit them
118 59
        $parent = get_parent_class($class);
119 59
        if ($parent) {
120 15
            $spec = $this->getUnified($parent);
121
        } else {
122 59
            $spec = new Blueprint($class);
123
        }
124
125
        // stores the unified params and setters
126 59
        $this->unified->set(
127 59
            $class,
128 59
            new Blueprint(
129 59
                $class,
130 59
                $this->getUnifiedParameters($class, $spec->getParameters()),
131 58
                $this->getUnifiedSetters($class, $spec->getSetters()),
132 58
                $this->getUnifiedMutations($class, $spec->getMutations())
133
            )
134
        );
135
136
        // done, return the unified values
137 58
        return $this->unified->get($class);
138
    }
139
140
    /**
141
     * @return ValueObject
142
     */
143 5
    public function mutations(): ValueObject
144
    {
145 5
        return $this->mutations;
146
    }
147
148
    /**
149
     * @return ValueObject
150
     */
151 19
    public function parameters(): ValueObject
152
    {
153 19
        return $this->parameters;
154
    }
155
156
    /**
157
     * Creates and returns a new instance of a class using reflection and
158
     * the configuration parameters, optionally with overrides, invoking Lazy
159
     * values along the way.
160
     *
161
     * @param Blueprint $blueprint The blueprint to be resolved containing
162
     *                             its overrides for this specific case.
163
     * @param array     $contextualBlueprints
164
     *
165
     * @return object
166
     * @throws NoSuchProperty
167
     * @throws ReflectionException
168
     */
169 46
    public function resolve(Blueprint $blueprint, array $contextualBlueprints = []): object
170
    {
171 46
        if ($contextualBlueprints === []) {
172 46
            return call_user_func(
173 46
                $this->expandParameters(
174
                    $this
175 46
                        ->getUnified($blueprint->getClassName())
176 45
                        ->merge($blueprint)
177
                ),
178 45
                $this->reflector->getClass($blueprint->getClassName())
179
            );
180
        }
181
182 1
        $remember = new self($this->reflector);
183
184 1
        foreach ($contextualBlueprints as $contextualBlueprint) {
185 1
            $className = $contextualBlueprint->getClassName();
186 1
            $this->processBlueprint($className, $remember, $this);
187
188 1
            $this->parameters->merge($className, $contextualBlueprint->getParameters());
189 1
            $this->setters->merge($className, $contextualBlueprint->getSetters());
190 1
            $this->mutations->merge($className, $contextualBlueprint->getMutations());
191
192 1
            $this->unified->remove($className);
193
        }
194
195 1
        $resolved = call_user_func(
196 1
            $this->expandParameters(
197 1
                $this->getUnified($blueprint->getClassName())->merge($blueprint)
198
            ),
199 1
            $this->reflector->getClass($blueprint->getClassName())
200
        );
201
202 1
        foreach ($contextualBlueprints as $contextualBlueprint) {
203 1
            $className = $contextualBlueprint->getClassName();
204 1
            $this->processBlueprint($className, $this, $remember);
205
206 1
            if ($remember->unified->has($className)) {
207
                $this->unified->set($className, $remember->unified->get($className));
208
            } else {
209 1
                $this->unified->remove($className);
210
            }
211
        }
212
213 1
        return $resolved;
214
    }
215
216
    /**
217
     * @return ValueObject
218
     */
219 13
    public function setters(): ValueObject
220
    {
221 13
        return $this->setters;
222
    }
223
224
    /**
225
     * @return ValueObject
226
     */
227 1
    public function unified(): ValueObject
228
    {
229 1
        return $this->unified;
230
    }
231
232
    /**
233
     * @return ValueObject
234
     */
235 5
    public function values(): ValueObject
236
    {
237 5
        return $this->values;
238
    }
239
240
    /**
241
     * Returns the unified mutations for a class.
242
     *
243
     * Class-specific mutations are executed last before trait-based mutations
244
     * and before interface-based mutations.
245
     *
246
     * @param string $class  The class name to return values for.
247
     * @param array  $parent The parent unified setters.
248
     *
249
     * @return array The unified mutations.
250
     * @throws NoSuchProperty
251
     */
252 58
    protected function getUnifiedMutations(string $class, array $parent): array
253
    {
254 58
        $unified = $parent;
255
256
        // look for interface mutations
257 58
        $interfaces = class_implements($class);
258 58
        foreach ($interfaces as $interface) {
259 4
            if ($this->mutations->has($interface)) {
260
                $unified = $this->mutations->merge($interface, $unified);
261
            }
262
        }
263
264
        // look for trait mutations
265 58
        $traits = $this->reflector->getTraits($class);
266 58
        foreach ($traits as $trait) {
267 5
            if ($this->mutations->has($trait)) {
268
                $unified = $this->mutations->merge($trait, $unified);
269
            }
270
        }
271
272
        // look for class mutations
273 58
        if ($this->mutations->has($class)) {
274 4
            $unified = array_merge(
275 4
                $unified,
276 4
                $this->mutations->get($class)
277
            );
278
        }
279
280 58
        return $unified;
281
    }
282
283
    /**
284
     * Returns a unified param.
285
     *
286
     * @param ReflectionParameter $rparam A parameter reflection.
287
     * @param string              $class  The class name to return values for.
288
     * @param array               $parent The parent unified params.
289
     *
290
     * @return mixed|DefaultValueParameter|UnresolvedParameter
291
     * @throws NoSuchProperty
292
     * @throws ReflectionException
293
     */
294 44
    protected function getUnifiedParameter(
295
        ReflectionParameter $rparam,
296
        string $class,
297
        array $parent
298
    ) {
299 44
        $name     = $rparam->getName();
300 44
        $position = $rparam->getPosition();
301
302
        // is there a positional value explicitly from the current class?
303 44
        $explicitPosition = $this->parameters->has($class)
304 44
            && array_key_exists($position, $this->parameters->get($class))
305 44
            && !$this->parameters->get($class)[$position] instanceof UnresolvedParameter;
306
307 44
        if ($explicitPosition) {
308 3
            return $this->parameters->get($class)[$position];
309
        }
310
311
        // is there a named value explicitly from the current class?
312 43
        $explicitNamed = $this->parameters->has($class)
313 43
            && array_key_exists($name, $this->parameters->get($class))
314 43
            && !$this->parameters->get($class)[$name] instanceof UnresolvedParameter;
315
316 43
        if ($explicitNamed) {
317 14
            return $this->parameters->get($class)[$name];
318
        }
319
320
        // is there a named value implicitly inherited from the parent class?
321
        // (there cannot be a positional parent. this is because the unified
322
        // values are stored by name, not position.)
323 33
        $implicitNamed = array_key_exists($name, $parent)
324 33
            && !$parent[$name] instanceof UnresolvedParameter
325 33
            && !$parent[$name] instanceof DefaultValueParameter;
326
327 33
        if ($implicitNamed) {
328 3
            return $parent[$name];
329
        }
330
331
        // is a default value available for the current class?
332 31
        if ($rparam->isDefaultValueAvailable()) {
333 24
            return new DefaultValueParameter($name, $rparam->getDefaultValue());
334
        }
335
336
        // is a default value available for the parent class?
337 17
        $parentDefault = array_key_exists($name, $parent)
338 17
            && $parent[$name] instanceof DefaultValueParameter;
339 17
        if ($parentDefault) {
340 9
            return $parent[$name];
341
        }
342
343
        // param is missing
344 9
        return new UnresolvedParameter($name);
345
    }
346
347
    /**
348
     * Returns the unified constructor params for a class.
349
     *
350
     * @param string $class  The class name to return values for.
351
     * @param array  $parent The parent unified params.
352
     *
353
     * @return array The unified params.
354
     * @throws NoSuchProperty
355
     * @throws ReflectionException
356
     */
357 59
    protected function getUnifiedParameters(string $class, array $parent): array
358
    {
359
        // reflect on what params to pass, in which order
360 59
        $unified = [];
361 59
        $rparams = $this->reflector->getParameters($class);
362 58
        foreach ($rparams as $rparam) {
363 44
            $unified[$rparam->name] = $this->getUnifiedParameter(
364 44
                $rparam,
365
                $class,
366
                $parent
367
            );
368
        }
369
370 58
        return $unified;
371
    }
372
373
    /**
374
     * Returns the unified setters for a class.
375
     *
376
     * Class-specific setters take precedence over trait-based setters, which
377
     * take precedence over interface-based setters.
378
     *
379
     * @param string $class  The class name to return values for.
380
     * @param array  $parent The parent unified setters.
381
     *
382
     * @return array The unified setters.
383
     * @throws NoSuchProperty
384
     */
385 58
    protected function getUnifiedSetters(string $class, array $parent): array
386
    {
387 58
        $unified = $parent;
388
        // look for interface setters
389 58
        $interfaces = class_implements($class);
390 58
        foreach ($interfaces as $interface) {
391 4
            if ($this->setters->has($interface)) {
392 1
                $unified = $this->setters->merge($interface, $unified);
393
            }
394
        }
395
396
        // look for trait setters
397 58
        $traits = $this->reflector->getTraits($class);
398 58
        foreach ($traits as $trait) {
399 5
            if ($this->setters->has($trait)) {
400 5
                $unified = $this->setters->merge($trait, $unified);
401
            }
402
        }
403
404
        // look for class setters
405 58
        if ($this->setters->has($class)) {
406 8
            $unified = array_merge(
407 8
                $unified,
408 8
                $this->setters->get($class)
409
            );
410
        }
411
412 58
        return $unified;
413
    }
414
415
    /**
416
     * Expands variadic parameters onto the end of a constructor parameters
417
     * array.
418
     *
419
     * @param Blueprint $blueprint The blueprint to expand parameters for.
420
     *
421
     * @return Blueprint The blueprint with expanded constructor parameters.
422
     * @throws ReflectionException
423
     */
424 45
    protected function expandParameters(Blueprint $blueprint): Blueprint
425
    {
426 45
        $class  = $blueprint->getClassName();
427 45
        $params = $blueprint->getParameters();
428
429 45
        $variadicParams = [];
430 45
        foreach ($this->reflector->getParameters($class) as $reflectParam) {
431 36
            $paramName = $reflectParam->getName();
432 36
            if ($reflectParam->isVariadic() && is_array($params[$paramName])) {
433 2
                $variadicParams = array_merge($variadicParams, $params[$paramName]);
434 2
                unset($params[$paramName]);
435 2
                break; // There can only be one
436
            }
437
438 36
            if ($params[$paramName] instanceof DefaultValueParameter) {
439 10
                $params[$paramName] = $params[$paramName]->getValue();
440
            }
441
        }
442
443 45
        return $blueprint->replaceParameters(
444 45
            array_merge($params, array_values($variadicParams))
445
        );
446
    }
447
448
    /**
449
     * @param string   $className
450
     * @param Resolver $source
451
     * @param Resolver $target
452
     */
453 1
    private function processBlueprint(
454
        string $className,
455
        Resolver $source,
456
        Resolver $target
457
    ): void {
458 1
        $source->parameters->set(
459 1
            $className,
460 1
            $target->parameters->getWithDefault($className, [])
461
        );
462 1
        $source->setters->set(
463 1
            $className,
464 1
            $target->setters->getWithDefault($className, [])
465
        );
466 1
        $source->mutations->set(
467 1
            $className,
468 1
            $target->mutations->getWithDefault($className, [])
469
        );
470 1
    }
471
}
472