Passed
Pull Request — master (#92)
by Sergei
02:35
created

ArrayDefinitionBuilder::getExtractor()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Factory\Definition;
6
7
use Psr\Container\ContainerInterface;
8
use Yiisoft\Factory\Exception\InvalidConfigException;
9
use Yiisoft\Factory\Exception\NotInstantiableException;
10
11
use function array_key_exists;
12
use function call_user_func_array;
13
use function is_string;
14
15
/**
16
 * @internal Builds object by ArrayDefinition.
17
 */
18
final class ArrayDefinitionBuilder
19
{
20
    private static ?self $instance = null;
21
22
    /**
23
     * @psalm-var array<string, array<string, DefinitionInterface>>
24
     */
25
    private static array $dependencies = [];
26
27 1
    private function __construct()
28
    {
29 1
    }
30
31 47
    public static function getInstance(): self
32
    {
33 47
        if (self::$instance === null) {
34 1
            self::$instance = new self();
35
        }
36
37 47
        return self::$instance;
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::instance could return the type null which is incompatible with the type-hinted return Yiisoft\Factory\Definition\ArrayDefinitionBuilder. Consider adding an additional type-check to rule them out.
Loading history...
38
    }
39
40
    /**
41
     * @throws NotInstantiableException
42
     * @throws InvalidConfigException
43
     */
44 47
    public function build(ContainerInterface $container, ArrayDefinition $definition): object
45
    {
46 47
        $class = $definition->getClass();
47 47
        $dependencies = $this->getDependencies($class);
48 44
        $constructorArguments = $definition->getConstructorArguments();
49
50 44
        $this->injectArguments($dependencies, $constructorArguments);
51
52 43
        $resolved = DefinitionResolver::resolveArray($container, $dependencies);
53
54
        /** @psalm-suppress MixedMethodCall */
55 41
        $object = new $class(...array_values($resolved));
56
57 41
        $methodsAndProperties = $definition->getMethodsAndProperties();
58 41
        foreach ($methodsAndProperties as $item) {
59
            /** @var mixed $value */
60 12
            [$type, $name, $value] = $item;
61
            /** @var mixed */
62 12
            $value = DefinitionResolver::resolve($container, $value);
63 12
            if ($type === ArrayDefinition::TYPE_METHOD) {
64
                /** @var mixed */
65 10
                $setter = call_user_func_array([$object, $name], $value);
66 10
                if ($setter instanceof $object) {
67
                    /** @var object */
68 10
                    $object = $setter;
69
                }
70 3
            } elseif ($type === ArrayDefinition::TYPE_PROPERTY) {
71 3
                $object->$name = $value;
72
            }
73
        }
74
75 41
        return $object;
76
    }
77
78
    /**
79
     * @psalm-param array<string, DefinitionInterface> $dependencies
80
     *
81
     * @throws InvalidConfigException
82
     */
83 44
    private function injectArguments(array &$dependencies, array $arguments): void
84
    {
85 44
        $isIntegerIndexed = $this->isIntegerIndexed($arguments);
86 43
        $dependencyIndex = 0;
87 43
        $usedArguments = [];
88 43
        $isVariadic = false;
89 43
        foreach ($dependencies as $key => &$value) {
90 41
            if ($value instanceof ParameterDefinition && $value->getParameter()->isVariadic()) {
91 22
                $isVariadic = true;
92
            }
93 41
            $index = $isIntegerIndexed ? $dependencyIndex : $key;
94 41
            if (array_key_exists($index, $arguments)) {
95 14
                $value = DefinitionResolver::ensureResolvable($arguments[$index]);
96 14
                $usedArguments[$index] = 1;
97
            }
98 41
            $dependencyIndex++;
99
        }
100 43
        unset($value);
101 43
        if ($isVariadic) {
102
            /** @var mixed $value */
103 22
            foreach ($arguments as $index => $value) {
104 9
                if (!isset($usedArguments[$index])) {
105 2
                    $dependencies[$index] = DefinitionResolver::ensureResolvable($value);
106
                }
107
            }
108
        }
109
        /** @psalm-var array<string, DefinitionInterface> $dependencies */
110 43
    }
111
112 44
    private function isIntegerIndexed(array $arguments): bool
113
    {
114 44
        $hasStringIndex = false;
115 44
        $hasIntegerIndex = false;
116
117 44
        foreach ($arguments as $index => $_argument) {
118 15
            if (is_string($index)) {
119 8
                $hasStringIndex = true;
120 8
                if ($hasIntegerIndex) {
121 8
                    break;
122
                }
123
            } else {
124 8
                $hasIntegerIndex = true;
125 8
                if ($hasStringIndex) {
126 1
                    break;
127
                }
128
            }
129
        }
130 44
        if ($hasIntegerIndex && $hasStringIndex) {
131 1
            throw new InvalidConfigException(
132 1
                'Arguments indexed both by name and by position are not allowed in the same array.'
133
            );
134
        }
135
136 43
        return $hasIntegerIndex;
137
    }
138
139
    /**
140
     * Returns the dependencies of the specified class.
141
     *
142
     * @param class-string $class Class name or interface 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...
143
     *
144
     * @throws NotInstantiableException
145
     *
146
     * @return DefinitionInterface[] The dependencies of the specified class.
147
     * @psalm-return array<string, DefinitionInterface>
148
     */
149 47
    private function getDependencies(string $class): array
150
    {
151 47
        if (!isset(self::$dependencies[$class])) {
152 13
            self::$dependencies[$class] = DefinitionExtractor::getInstance()->fromClassName($class);
153
        }
154
155 44
        return self::$dependencies[$class];
156
    }
157
}
158