Passed
Push — master ( ce8019...6eec84 )
by Alexander
01:54
created

ArrayBuilder::configure()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4.0312

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 8
c 1
b 0
f 0
nc 4
nop 3
dl 0
loc 16
ccs 7
cts 8
cp 0.875
crap 4.0312
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Factory\Definitions;
6
7
use Psr\Container\ContainerInterface;
8
use Yiisoft\Factory\Exceptions\InvalidConfigException;
9
use Yiisoft\Factory\Exceptions\NotInstantiableException;
10
use Yiisoft\Factory\Extractors\DefinitionExtractor;
11
12
/**
13
 * Builds object by ArrayDefinition.
14
 */
15
class ArrayBuilder
16
{
17
    private static ?DefinitionExtractor $extractor = null;
18
    private static array $dependencies = [];
19
20
    /**
21
     * @param ContainerInterface $container
22
     * @param ArrayDefinition $definition
23
     * @return object
24
     * @throws NotInstantiableException
25
     * @throws InvalidConfigException
26
     */
27 44
    public function build(ContainerInterface $container, ArrayDefinition $definition)
28
    {
29 44
        $class = $definition->getClass();
30 44
        $dependencies = $this->getDependencies($class);
31 41
        $parameters = $definition->getParams();
32 41
        $this->injectParameters($dependencies, $parameters);
33 39
        $resolved = DefinitionResolver::resolveArray($container, $dependencies);
34 37
        $object = new $class(...array_values($resolved));
35 37
        $config = DefinitionResolver::resolveArray($container, $definition->getConfig());
36
37 37
        return $this->configure($container, $object, $config);
38
    }
39
40
    /**
41
     * @param array $dependencies
42
     * @param array $parameters
43
     * @throws InvalidConfigException
44
     */
45 41
    private function injectParameters(array &$dependencies, array $parameters): void
46
    {
47 41
        $isIntegerIndexed = $this->isIntegerIndexed($parameters);
48 39
        $dependencyIndex = 0;
49 39
        $usedParameters = [];
50 39
        $isVariadic = false;
51 39
        foreach ($dependencies as $key => &$value) {
52 38
            if ($value instanceof ParameterDefinition && $value->getParameter()->isVariadic()) {
53
                $isVariadic = true;
54
            }
55 38
            $index = $isIntegerIndexed ? $dependencyIndex : $key;
56 38
            if (array_key_exists($index, $parameters)) {
57 10
                $value = DefinitionResolver::ensureResolvable($parameters[$index]);
58 10
                $usedParameters[$index] = 1;
59
            }
60 38
            $dependencyIndex++;
61
        }
62 39
        unset($value);
63 39
        if ($isVariadic) {
64
            foreach ($parameters as $index => $value) {
65
                if (!isset($usedParameters[$index])) {
66
                    $dependencies[$index] = DefinitionResolver::ensureResolvable($value);
67
                }
68
            }
69
        }
70 39
    }
71
72 41
    private function isIntegerIndexed(array $parameters): bool
73
    {
74 41
        $hasStringIndex = false;
75 41
        $hasIntegerIndex = false;
76
77 41
        foreach ($parameters as $index => $parameter) {
78 12
            if (is_string($index)) {
79 8
                $hasStringIndex = true;
80 8
                if ($hasIntegerIndex) {
81 8
                    break;
82
                }
83
            } else {
84 6
                $hasIntegerIndex = true;
85 6
                if ($hasStringIndex) {
86 2
                    break;
87
                }
88
            }
89
        }
90 41
        if ($hasIntegerIndex && $hasStringIndex) {
91 2
            throw new InvalidConfigException(
92 2
                'Parameters indexed both by name and by position are not allowed in the same array.'
93
            );
94
        }
95
96 39
        return $hasIntegerIndex;
97
    }
98
99
    /**
100
     * Returns the dependencies of the specified class.
101
     * @param class-string $class class name, interface name or alias name
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
102
     * @return DefinitionInterface[] the dependencies of the specified class.
103
     * @throws NotInstantiableException
104
     * @internal
105
     */
106 44
    private function getDependencies(string $class): array
107
    {
108 44
        if (!isset(self::$dependencies[$class])) {
109 10
            self::$dependencies[$class] = $this->getExtractor()->fromClassName($class);
110
        }
111
112 41
        return self::$dependencies[$class];
113
    }
114
115 10
    private function getExtractor(): DefinitionExtractor
116
    {
117 10
        if (static::$extractor === null) {
0 ignored issues
show
Bug introduced by
Since $extractor is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $extractor to at least protected.
Loading history...
118
            // For now use hard coded extractor.
119 1
            static::$extractor = new DefinitionExtractor();
120
        }
121
122 10
        return static::$extractor;
0 ignored issues
show
Bug Best Practice introduced by
The expression return static::extractor could return the type null which is incompatible with the type-hinted return Yiisoft\Factory\Extractors\DefinitionExtractor. Consider adding an additional type-check to rule them out.
Loading history...
123
    }
124
125
    /**
126
     * Configures an object with the given configuration.
127
     * @param ContainerInterface $container
128
     * @param object $object the object to be configured
129
     * @param iterable $config property values and methods to call
130
     * @return object the object itself
131
     */
132 37
    private function configure(ContainerInterface $container, $object, iterable $config)
133
    {
134 37
        foreach ($config as $action => $arguments) {
135 5
            if (substr($action, -2) === '()') {
136
                // method call
137 5
                $setter = \call_user_func_array([$object, substr($action, 0, -2)], $arguments);
138 5
                if ($setter instanceof $object) {
139 5
                    $object = $setter;
140
                }
141
            } else {
142
                // property
143
                $object->$action = $arguments;
144
            }
145
        }
146
147 37
        return $object;
148
    }
149
}
150