Test Failed
Pull Request — master (#78)
by Alexander
02:13
created

ArrayDefinition::setMethodCalls()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 7.7656

Importance

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