Passed
Push — master ( 5e9345...9dff6c )
by Alexander
02:08 queued 38s
created

ArrayBuilder::injectParameter()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 4
nop 3
dl 0
loc 14
ccs 11
cts 11
cp 1
crap 4
rs 9.9
c 0
b 0
f 0
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 42
    public function build(ContainerInterface $container, ArrayDefinition $definition)
28
    {
29 42
        $class = $definition->getClass();
30 42
        $dependencies = $this->getDependencies($class);
31 39
        $parameters = $definition->getParams();
32 39
        $this->injectParameters($dependencies, $parameters);
33 37
        $resolved = DefinitionResolver::resolveArray($container, $dependencies);
34 35
        $object = new $class(...array_values($resolved));
35
36 35
        return $this->configure($container, $object, $definition->getConfig());
37
    }
38
39
    /**
40
     * @param array $dependencies
41
     * @param array $parameters
42
     * @throws InvalidConfigException
43
     */
44 39
    private function injectParameters(array &$dependencies, array $parameters): void
45
    {
46 39
        $isIntegerIndexed = $this->isIntegerIndexed($parameters);
47 37
        $dependencyIndex = 0;
48 37
        $usedParameters = [];
49 37
        $isVariadic = false;
50 37
        foreach ($dependencies as $key => &$value) {
51 14
            if ($value instanceof ParameterDefinition && $value->getParameter()->isVariadic()) {
52
                $isVariadic = true;
53
            }
54 14
            $index = $isIntegerIndexed ? $dependencyIndex : $key;
55 14
            if (array_key_exists($index, $parameters)) {
56 8
                $value = DefinitionResolver::ensureResolvable($parameters[$index]);
57 8
                $usedParameters[$index] = 1;
58
            }
59 14
            $dependencyIndex++;
60
        }
61 37
        unset($value);
62 37
        if ($isVariadic) {
63
            foreach ($parameters as $index => $value) {
64
                if (!isset($usedParameters[$index])) {
65
                    $dependencies[$index] = DefinitionResolver::ensureResolvable($value);
66
                }
67
            }
68
        }
69 37
    }
70
71 39
    private function isIntegerIndexed(array $parameters): bool
72
    {
73 39
        $hasStringIndex = false;
74 39
        $hasIntegerIndex = false;
75
76 39
        foreach ($parameters as $index => $parameter) {
77 10
            if (is_string($index)) {
78 6
                $hasStringIndex = true;
79 6
                if ($hasIntegerIndex) {
80 6
                    break;
81
                }
82
            } else {
83 6
                $hasIntegerIndex = true;
84 6
                if ($hasStringIndex) {
85 2
                    break;
86
                }
87
            }
88
        }
89 39
        if ($hasIntegerIndex && $hasStringIndex) {
90 2
            throw new InvalidConfigException(
91 2
                'Parameters indexed both by name and by position are not allowed in the same array.'
92
            );
93
        }
94
95 37
        return $hasIntegerIndex;
96
    }
97
98
    /**
99
     * Returns the dependencies of the specified class.
100
     * @param string $class class name, interface name or alias name
101
     * @return DefinitionInterface[] the dependencies of the specified class.
102
     * @throws NotInstantiableException
103
     * @internal
104
     */
105 42
    private function getDependencies(string $class): array
106
    {
107 42
        if (!isset(self::$dependencies[$class])) {
108 10
            self::$dependencies[$class] = $this->getExtractor()->fromClassName($class);
109
        }
110
111 39
        return self::$dependencies[$class];
112
    }
113
114 10
    private function getExtractor(): DefinitionExtractor
115
    {
116 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...
117
            // For now use hard coded extractor.
118 1
            static::$extractor = new DefinitionExtractor();
119
        }
120
121 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...
122
    }
123
124
    /**
125
     * Configures an object with the given configuration.
126
     * @param ContainerInterface $container
127
     * @param object $object the object to be configured
128
     * @param iterable $config property values and methods to call
129
     * @return object the object itself
130
     */
131 35
    private function configure(ContainerInterface $container, $object, iterable $config)
132
    {
133 35
        foreach ($config as $action => $arguments) {
134 5
            if (substr($action, -2) === '()') {
135
                // method call
136 5
                $setter = \call_user_func_array([$object, substr($action, 0, -2)], $arguments);
137 5
                if ($setter instanceof $object) {
138 5
                    $object = $setter;
139
                }
140
            } else {
141
                // property
142
                if ($arguments instanceof DefinitionInterface) {
143
                    $arguments = $arguments->resolve($container);
144
                }
145
                $object->$action = $arguments;
146
            }
147
        }
148
149 35
        return $object;
150
    }
151
}
152