Passed
Pull Request — master (#8)
by Sergei
02:40
created

ArrayDefinitionBuilder::isIntegerIndexed()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7

Importance

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