Passed
Pull Request — master (#78)
by Dmitriy
07:47
created

ArrayDefinition::extractMethods()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 5
nc 3
nop 1
dl 0
loc 11
ccs 6
cts 6
cp 1
crap 4
rs 10
c 0
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\InvalidConfigException;
9
10
use Yiisoft\Factory\Exceptions\NotInstantiableException;
11
12
use function array_key_exists;
13
use function get_class;
14
use function gettype;
15
use function is_array;
16
use function is_int;
17
use function is_object;
18
use function is_string;
19
20
/**
21
 * Builds object by array config
22
 */
23
class ArrayDefinition implements DefinitionInterface
24
{
25
    public const CLASS_NAME = 'class';
26
    public const CONSTRUCTOR = '__construct()';
27
28
    /**
29
     * @psalm-var class-string
30
     */
31
    private string $class;
32
    private array $constructorParameters;
33
34
    /**
35
     * @psalm-var array<string, array>
36
     */
37
    private array $callMethods;
38
39
    /**
40
     * @psalm-var array<string, mixed>
41
     */
42
    private array $setProperties;
43
44
    /**
45
     * @psalm-param array{
46
     *   class: class-string,
47
     *   constructor?: array,
48
     *   callMethods?: array,
49
     *   setProperties?: array,
50
     * } $config
51
     *
52
     * @throws InvalidConfigException
53
     */
54 36
    public function __construct(array $config)
55
    {
56 36
        $this->setClass($config);
57 29
        $this->setConstructorParameters($config);
58 28
        $this->setCallMethods($config);
59 27
        $this->setSetProperties($config);
60 27
    }
61
62
    /**
63
     * @throws InvalidConfigException
64
     */
65 36
    private function setClass(array $config): void
66
    {
67 36
        if (!array_key_exists(self::CLASS_NAME, $config)) {
68 1
            throw new InvalidConfigException('Invalid definition: no class name specified.');
69
        }
70
71
        /** @var mixed */
72 35
        $class = $config[self::CLASS_NAME];
73
74 35
        if (!is_string($class)) {
75 1
            throw new InvalidConfigException(sprintf('Invalid definition: invalid class name "%s".', (string)$class));
76
        }
77
78 34
        if ($class === '') {
79 1
            throw new InvalidConfigException('Invalid definition: empty class name.');
80
        }
81
82 33
        if (!class_exists($class)) {
83 6
            throw new InvalidConfigException(sprintf('Invalid definition: class "%s" does not exist.', $class));
84
        }
85
86 29
        $this->class = $class;
87 29
    }
88
89
    /**
90
     * @throws InvalidConfigException
91
     */
92 29
    private function setConstructorParameters(array $config): void
93
    {
94 29
        $parameters = $config[self::CONSTRUCTOR] ?? [];
95
96 29
        if (!is_array($parameters)) {
97 1
            throw new InvalidConfigException(
98 1
                sprintf(
99 1
                    'Invalid definition: incorrect constructor parameters. Expected array, got %s.',
100 1
                    $this->getType($parameters)
101
                )
102
            );
103
        }
104
105 28
        $this->constructorParameters = $parameters;
106 28
    }
107
108
    /**
109
     * @throws InvalidConfigException
110
     */
111 28
    private function setCallMethods(array $config): void
112
    {
113 28
        $items = $this->extractMethods($config);
114
115 28
        if (!is_array($items)) {
0 ignored issues
show
introduced by
The condition is_array($items) is always true.
Loading history...
116
            throw new InvalidConfigException(
117
                sprintf('Invalid definition: incorrect method calls. Expected array, got %s.', $this->getType($items))
118
            );
119
        }
120
121 28
        $callMethods = [];
122 28
        foreach ($items as $key => $value) {
123 8
            if (is_int($key)) {
124
                if (!is_string($value)) {
125
                    throw new InvalidConfigException(
126
                        sprintf('Invalid definition: expected method name, got %s.', $this->getType($value))
127
                    );
128
                }
129
                if ($value === '') {
130
                    throw new InvalidConfigException('Invalid definition: expected method name, got empty string.');
131
                }
132
                $callMethods[$value] = [];
133
            } else {
134 8
                if (!is_array($value)) {
135 1
                    throw new InvalidConfigException(
136 1
                        sprintf('Invalid definition: incorrect method parameters. Expected array, got %s.', $this->getType($value))
137
                    );
138
                }
139 7
                $callMethods[$key] = $value;
140
            }
141
        }
142
143 27
        $this->callMethods = $callMethods;
144 27
    }
145
146 28
    private function extractMethods(array $config): array
147
    {
148 28
        $methods = [];
149
150 28
        foreach ($config as $key => $value) {
151 28
            if ($key !== self::CONSTRUCTOR && substr($key, -2) === '()') {
152 8
                $methods[substr($key, 0, -2)] = $value;
153
            }
154
        }
155
156 28
        return $methods;
157
    }
158
159
    /**
160
     * @throws InvalidConfigException
161
     */
162 27
    private function setSetProperties(array $config): void
163
    {
164 27
        $properties = $this->extractProperties($config);
165
166 27
        if (!is_array($properties)) {
0 ignored issues
show
introduced by
The condition is_array($properties) is always true.
Loading history...
167
            throw new InvalidConfigException(
168
                sprintf('Invalid definition: incorrect properties to set. Expected array, got %s.', $this->getType($properties))
169
            );
170
        }
171
172 27
        foreach ($properties as $key => $_value) {
173 2
            if (!is_string($key)) {
174
                throw new InvalidConfigException(
175
                    sprintf('Invalid definition: expected property name, got %s.', $this->getType($key))
176
                );
177
            }
178
        }
179
180
        /** @psalm-var array<string,mixed> $properties */
181
182 27
        $this->setProperties = $properties;
183 27
    }
184
185 27
    private function extractProperties(array $config): array
186
    {
187 27
        $properties = [];
188 27
        foreach ($config as $key => $value) {
189 27
            if (substr($key, 0, 1) === '@') {
190 2
                $properties[substr($key, 1)] = $value;
191
            }
192
        }
193
194 27
        return $properties;
195
    }
196
197
    /**
198
     * @psalm-return class-string
199
     */
200 27
    public function getClass(): string
201
    {
202 27
        return $this->class;
203
    }
204
205 27
    public function getConstructorParameters(): array
206
    {
207 27
        return $this->constructorParameters;
208
    }
209
210
    /**
211
     * @psalm-return array<string, array>
212
     */
213 25
    public function getCallMethods(): array
214
    {
215 25
        return $this->callMethods;
216
    }
217
218
    /**
219
     * @psalm-return array<string, mixed>
220
     */
221 25
    public function getSetProperties(): array
222
    {
223 25
        return $this->setProperties;
224
    }
225
226
    /**
227
     * @throws NotInstantiableException
228
     * @throws InvalidConfigException
229
     */
230 27
    public function resolve(ContainerInterface $container): object
231
    {
232 27
        return ArrayDefinitionBuilder::getInstance()->build($container, $this);
233
    }
234
235
    public function merge(self $other): self
236
    {
237
        $callMethods = $this->getCallMethods();
238
        foreach ($other->getCallMethods() as $method => $parameters) {
239
            $callMethods[$method] = isset($callMethods[$method])
240
                ? $this->mergeParameters($callMethods[$method], $parameters)
241
                : $parameters;
242
        }
243
        $mergedMethods = [];
244
        foreach ($callMethods as $key => $value) {
245
            $mergedMethods[$key . '()'] = $value;
246
        }
247
248
        $properties = array_merge($this->getSetProperties(), $other->getSetProperties());
249
        $mergedProperties = [];
250
        foreach ($properties as $key => $value) {
251
            $mergedProperties['@' . $key] = $value;
252
        }
253
254
        return new self(array_merge([
255
            self::CLASS_NAME => $other->getClass(),
256
            self::CONSTRUCTOR => $this->mergeParameters(
257
                $this->getConstructorParameters(),
258
                $other->getConstructorParameters()
259
            ),
260
        ], $mergedMethods, $mergedProperties));
261
    }
262
263
    private function mergeParameters(array $selfParameters, array $otherParameters): array
264
    {
265
        /** @var mixed $parameter */
266
        foreach ($otherParameters as $index => $parameter) {
267
            /** @var mixed */
268
            $selfParameters[$index] = $parameter;
269
        }
270
271
        return $selfParameters;
272
    }
273
274
    /**
275
     * @param mixed $value
276
     */
277 2
    private function getType($value): string
278
    {
279 2
        return is_object($value) ? get_class($value) : gettype($value);
280
    }
281
}
282