Passed
Pull Request — master (#12)
by Dmitriy
02:20
created

ArrayDefinitionBuilder::getDependencies()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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