Passed
Push — master ( cfe928...9076e1 )
by Alexander
10:06 queued 08:36
created

ArrayBuilder::build()   A

Complexity

Conditions 5
Paths 2

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 5
eloc 13
nc 2
nop 2
dl 0
loc 21
ccs 13
cts 13
cp 1
crap 5
rs 9.5222
c 4
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\NotInstantiableException;
9
use Yiisoft\Factory\Resolvers\ClassNameResolver;
10
11
/**
12
 * Builds object by ArrayDefinition.
13
 */
14
class ArrayBuilder
15
{
16
    private static $dependencies = [];
17
18 42
    public function build(ContainerInterface $container, ArrayDefinition $definition)
19
    {
20 42
        $class = $definition->getClass();
21 42
        $dependencies = $this->getDependencies($class);
22 39
        $parameters = $definition->getParams();
23
24 39
        if (!empty($parameters)) {
25 10
            $this->validateParameters($parameters);
26
27 8
            foreach ($parameters as $index => $parameter) {
28 8
                if ($parameter instanceof ReferenceInterface || is_array($parameter)) {
29 2
                    $this->injectParameter($dependencies, $index, $parameter);
30
                } else {
31 6
                    $this->injectParameter($dependencies, $index, new ValueDefinition($parameter));
32
                }
33
            }
34
        }
35
36 37
        $resolved = $this->resolveDependencies($container, $dependencies);
37 35
        $object = new $class(...array_values($resolved));
38 35
        return $this->configure($container, $object, $definition->getConfig());
39
    }
40
41 10
    private function validateParameters(array $parameters): void
42
    {
43 10
        $hasStringParameter = false;
44 10
        $hasIntParameter = false;
45 10
        foreach ($parameters as $index => $parameter) {
46 10
            if (is_string($index)) {
47 6
                $hasStringParameter = true;
48 6
                if ($hasIntParameter) {
49 6
                    break;
50
                }
51
            } else {
52 6
                $hasIntParameter = true;
53 6
                if ($hasStringParameter) {
54 2
                    break;
55
                }
56
            }
57
        }
58 10
        if ($hasIntParameter && $hasStringParameter) {
59 2
            throw new \InvalidArgumentException(
60 2
                'Parameters indexed by name and by position in the same array are not allowed.'
61
            );
62
        }
63 8
    }
64
65 8
    private function injectParameter(array &$dependencies, $index, $parameter): void
66
    {
67 8
        if (is_string($index)) {
68 4
            $dependencies[$index] = $parameter;
69
        } else {
70 4
            reset($dependencies);
71 4
            $dependencyIndex = 0;
72 4
            while (current($dependencies)) {
73 4
                if ($index === $dependencyIndex) {
74 4
                    $dependencies[key($dependencies)] = $parameter;
75 4
                    break;
76
                }
77 2
                next($dependencies);
78 2
                $dependencyIndex++;
79
            }
80
        }
81 8
    }
82
83
    /**
84
     * Resolves dependencies by replacing them with the actual object instances.
85
     * @param ContainerInterface $container
86
     * @param DefinitionInterface[] $dependencies the dependencies
87
     * @return array the resolved dependencies
88
     */
89 37
    private function resolveDependencies(ContainerInterface $container, array $dependencies): array
90
    {
91 37
        $result = [];
92 37
        foreach ($dependencies as $key => $dependency) {
93 14
            $result[$key] = $this->resolveDependency($container, $dependency);
94
        }
95
96 35
        return $result;
97
    }
98
99
    /**
100
     * This function resolves a dependency recursively, checking for loops.
101
     * TODO add checking for loops
102
     * @param ContainerInterface $container
103
     * @param mixed $dependency
104
     * @return mixed
105
     */
106 14
    private function resolveDependency(ContainerInterface $container, $dependency)
107
    {
108 14
        if ($dependency instanceof DefinitionInterface) {
109 14
            $dependency = $dependency->resolve($container);
110 2
        } elseif (is_array($dependency)) {
111 2
            return $this->resolveDependencies($container, $dependency);
112
        }
113
114 12
        return $dependency;
115
    }
116
117
    /**
118
     * Returns the dependencies of the specified class.
119
     * @param string $class class name, interface name or alias name
120
     * @return DefinitionInterface[] the dependencies of the specified class.
121
     * @throws NotInstantiableException
122
     * @internal
123
     */
124 42
    private function getDependencies(string $class): array
125
    {
126 42
        if (!isset(self::$dependencies[$class])) {
127 10
            self::$dependencies[$class] = $this->getResolver()->resolveConstructor($class);
128
        }
129
130 39
        return self::$dependencies[$class];
131
    }
132
133
    private static $resolver;
134
135 10
    private function getResolver(): ClassNameResolver
136
    {
137 10
        if (static::$resolver === null) {
0 ignored issues
show
Bug introduced by
Since $resolver 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 $resolver to at least protected.
Loading history...
138
            // For now use hard coded resolver.
139 1
            static::$resolver = new ClassNameResolver();
140
        }
141
142 10
        return static::$resolver;
143
    }
144
145
    /**
146
     * Configures an object with the given configuration.
147
     * @param ContainerInterface $container
148
     * @param object $object the object to be configured
149
     * @param iterable $config property values and methods to call
150
     * @return object the object itself
151
     */
152 35
    private function configure(ContainerInterface $container, $object, iterable $config)
153
    {
154 35
        foreach ($config as $action => $arguments) {
155 5
            if (substr($action, -2) === '()') {
156
                // method call
157 5
                $setter = \call_user_func_array([$object, substr($action, 0, -2)], $arguments);
158 5
                if ($setter instanceof $object) {
159 5
                    $object = $setter;
160
                }
161
            } else {
162
                // property
163
                if ($arguments instanceof DefinitionInterface) {
164
                    $arguments = $arguments->resolve($container);
165
                }
166
                $object->$action = $arguments;
167
            }
168
        }
169
170 35
        return $object;
171
    }
172
}
173