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

ArrayDefinition::setMethodsAndProperties()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5

Importance

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