Passed
Pull Request — master (#85)
by Sergei
02:04
created

ArrayDefinition::mergeArguments()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 9
ccs 4
cts 4
cp 1
rs 10
cc 2
nc 2
nop 2
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 get_class;
13
use function gettype;
14
use function is_array;
15
use function is_object;
16
use function is_string;
17
18
/**
19
 * Builds object by array config
20
 */
21
class ArrayDefinition implements DefinitionInterface
22
{
23
    public const CLASS_NAME = 'class';
24
    public const CONSTRUCTOR = '__construct()';
25
26
    /**
27
     * @psalm-var class-string
28
     */
29
    private string $class;
30
    private array $constructorArguments;
31
32
    /**
33
     * @psalm-var array<string, mixed>
34
     */
35
    private array $methodsAndProperties;
36
37
    /**
38
     * @param array $config Container entry config.
39
     * @param bool $checkDefinition Check definition flag.
40
     *
41
     * @throws InvalidConfigException
42
     */
43 49
    public function __construct(array $config)
44
    {
45 49
        foreach ($config as $key => $_value) {
46 48
            if (!is_string($key)) {
47
                throw new InvalidConfigException('Invalid definition: keys should be string.');
48
            }
49
        }
50
51
        /** @psalm-var array<string, mixed> $config */
52
53 49
        $this->setClass($config);
54 43
        $this->setConstructorArguments($config);
55 42
        $this->setMethodsAndProperties($config);
56
57 41
        if ($config !== []) {
58 2
            $key = array_key_first($config);
59 2
            throw new InvalidConfigException(
60 2
                sprintf(
61 2
                    'Invalid definition: key "%s" is not allowed. Did you mean "%s()" or "$%s"?',
62
                    $key,
63
                    $key,
64
                    $key
65
                )
66
            );
67
        }
68 39
    }
69
70
    /**
71
     * @psalm-param array<string, mixed> $config
72
     *
73
     * @throws InvalidConfigException
74
     */
75 49
    private function setClass(array &$config): void
76
    {
77 49
        if (!array_key_exists(self::CLASS_NAME, $config)) {
78 1
            throw new InvalidConfigException('Invalid definition: no class name specified.');
79
        }
80
81
        /** @var mixed */
82 48
        $class = $config[self::CLASS_NAME];
83 48
        unset($config[self::CLASS_NAME]);
84
85 48
        if (!is_string($class)) {
86 1
            throw new InvalidConfigException(sprintf('Invalid definition: invalid class name "%s".', (string)$class));
87
        }
88
89 47
        if ($class === '') {
90 1
            throw new InvalidConfigException('Invalid definition: empty class name.');
91
        }
92
93 46
        if (!class_exists($class)) {
94 5
            throw new InvalidConfigException(sprintf('Invalid definition: class "%s" does not exist.', $class));
95
        }
96
97 43
        $this->class = $class;
98 43
    }
99
100
    /**
101
     * @psalm-param array<string, mixed> $config
102
     *
103
     * @throws InvalidConfigException
104
     */
105 43
    private function setConstructorArguments(array &$config): void
106
    {
107 43
        if (!isset($config[self::CONSTRUCTOR])) {
108 31
            $this->constructorArguments = [];
109 31
            return;
110
        }
111
112 14
        $arguments = $config[self::CONSTRUCTOR];
113 14
        unset($config[self::CONSTRUCTOR]);
114
115 14
        if (!is_array($arguments)) {
116 1
            throw new InvalidConfigException(
117 1
                sprintf(
118 1
                    'Invalid definition: incorrect constructor arguments. Expected array, got %s.',
119 1
                    $this->getType($arguments)
120
                )
121
            );
122
        }
123
124 13
        $this->constructorArguments = $arguments;
125 13
    }
126
127
    /**
128
     * @psalm-param array<string, mixed> $config
129
     *
130
     * @throws InvalidConfigException
131
     */
132 42
    private function setMethodsAndProperties(array &$config): void
133
    {
134 42
        $methodsAndProperties = [];
135
136 42
        foreach ($config as $key => $value) {
137 16
            if (substr($key, -2) === '()') {
138 12
                if (!is_array($value)) {
139 1
                    throw new InvalidConfigException(
140 1
                        sprintf('Invalid definition: incorrect method arguments. Expected array, got %s.', $this->getType($value))
141
                    );
142
                }
143
144
                /** @var string $methodName */
145 11
                $methodName = substr($key, 0, -2);
146
147 11
                $methodsAndProperties[] = [ArrayDefinitionBuilder::METHOD, $methodName, $value];
148 11
                unset($config[$key]);
149 5
            } elseif (strncmp($key, '$', 1) === 0) {
150
                /** @var string $propertyName */
151 3
                $propertyName = substr($key, 1);
152
153 3
                $methodsAndProperties[$key] = [ArrayDefinitionBuilder::PROPERTY, $propertyName, $value];
154 3
                unset($config[$key]);
155
            }
156
        }
157
158 41
        $this->methodsAndProperties = $methodsAndProperties;
159 41
    }
160
161
    /**
162
     * @psalm-return class-string
163
     */
164 39
    public function getClass(): string
165
    {
166 39
        return $this->class;
167
    }
168
169 39
    public function getConstructorArguments(): array
170
    {
171 39
        return $this->constructorArguments;
172
    }
173
174 36
    public function getMethodsAndProperties(): array
175
    {
176 36
        return $this->methodsAndProperties;
177
    }
178
179
    /**
180
     * @throws NotInstantiableException
181
     * @throws InvalidConfigException
182
     */
183 39
    public function resolve(ContainerInterface $container): object
184
    {
185 39
        return ArrayDefinitionBuilder::getInstance()->build($container, $this);
186
    }
187
188 2
    public function merge(self $other): self
189
    {
190 2
        $new = clone $this;
191 2
        $new->class = $other->class;
192 2
        $new->constructorArguments = $this->mergeArguments($this->constructorArguments, $other->constructorArguments);
193
194 2
        $methodsAndProperties = $this->methodsAndProperties;
195 2
        foreach ($other->methodsAndProperties as $key => $item) {
196 1
            if ($item[0] === ArrayDefinitionBuilder::PROPERTY) {
197
                $methodsAndProperties[$key] = $item;
198 1
            } elseif ($item[0] === ArrayDefinitionBuilder::METHOD) {
199 1
                $methodsAndProperties[$key] = [
200 1
                    $item[0],
201 1
                    $item[1],
202 1
                    isset($methodsAndProperties[$key])
203 1
                        ? $this->mergeArguments($methodsAndProperties[$key][2], $item[2])
204 1
                        : $item[2],
205
                ];
206
            }
207
        }
208 2
        $new->methodsAndProperties = $methodsAndProperties;
209
210 2
        return $new;
211
    }
212
213 2
    private function mergeArguments(array $selfArguments, array $otherArguments): array
214
    {
215
        /** @var mixed $argument */
216 2
        foreach ($otherArguments as $name => $argument) {
217
            /** @var mixed */
218 1
            $selfArguments[$name] = $argument;
219
        }
220
221 2
        return $selfArguments;
222
    }
223
224
    /**
225
     * @param mixed $value
226
     */
227 2
    private function getType($value): string
228
    {
229 2
        return is_object($value) ? get_class($value) : gettype($value);
230
    }
231
}
232