Passed
Pull Request — master (#85)
by Sergei
02:08
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
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
 * @psalm-type MethodOrPropertyItem = array{0:string,1:string,2:mixed}
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, MethodOrPropertyItem>
36
     */
37
    private array $methodsAndProperties;
38
39
    /**
40
     * @param array $config Container entry config.
41
     * @param bool $checkDefinition Check definition flag.
42
     *
43
     * @throws InvalidConfigException
44
     */
45 52
    public function __construct(array $config)
46
    {
47 52
        foreach ($config as $key => $_value) {
48 51
            if (!is_string($key)) {
49 1
                throw new InvalidConfigException('Invalid definition: keys should be string.');
50
            }
51
        }
52
53
        /** @psalm-var array<string, mixed> $config */
54
55 51
        $this->setClass($config);
56 48
        $this->setConstructorArguments($config);
57 47
        $this->setMethodsAndProperties($config);
58
59 46
        if ($config !== []) {
60 2
            $key = array_key_first($config);
61 2
            throw new InvalidConfigException(
62 2
                sprintf(
63 2
                    'Invalid definition: key "%s" is not allowed. Did you mean "%s()" or "$%s"?',
64
                    $key,
65
                    $key,
66
                    $key
67
                )
68
            );
69
        }
70 44
    }
71
72
    /**
73
     * @psalm-param array<string, mixed> $config
74
     *
75
     * @throws InvalidConfigException
76
     */
77 51
    private function setClass(array &$config): void
78
    {
79 51
        if (!array_key_exists(self::CLASS_NAME, $config)) {
80 1
            throw new InvalidConfigException('Invalid definition: no class name specified.');
81
        }
82
83
        /** @var mixed */
84 50
        $class = $config[self::CLASS_NAME];
85 50
        unset($config[self::CLASS_NAME]);
86
87 50
        if (!is_string($class)) {
88 1
            throw new InvalidConfigException(sprintf('Invalid definition: invalid class name "%s".', (string)$class));
89
        }
90
91 49
        if ($class === '') {
92 1
            throw new InvalidConfigException('Invalid definition: empty class name.');
93
        }
94
95
        /** @psalm-var class-string $class */
96
97 48
        $this->class = $class;
98 48
    }
99
100
    /**
101
     * @psalm-param array<string, mixed> $config
102
     *
103
     * @throws InvalidConfigException
104
     */
105 48
    private function setConstructorArguments(array &$config): void
106
    {
107 48
        if (!isset($config[self::CONSTRUCTOR])) {
108 35
            $this->constructorArguments = [];
109 35
            return;
110
        }
111
112 15
        $arguments = $config[self::CONSTRUCTOR];
113 15
        unset($config[self::CONSTRUCTOR]);
114
115 15
        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 14
        $this->constructorArguments = $arguments;
125 14
    }
126
127
    /**
128
     * @psalm-param array<string, mixed> $config
129
     *
130
     * @throws InvalidConfigException
131
     */
132 47
    private function setMethodsAndProperties(array &$config): void
133
    {
134 47
        $methodsAndProperties = [];
135
136 47
        foreach ($config as $key => $value) {
137 17
            if (substr($key, -2) === '()') {
138 13
                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 12
                $methodName = substr($key, 0, -2);
146
147 12
                $methodsAndProperties[$key] = [ArrayDefinitionBuilder::METHOD, $methodName, $value];
148 12
                unset($config[$key]);
149 6
            } elseif (strncmp($key, '$', 1) === 0) {
150
                /** @var string $propertyName */
151 4
                $propertyName = substr($key, 1);
152
153 4
                $methodsAndProperties[$key] = [ArrayDefinitionBuilder::PROPERTY, $propertyName, $value];
154 4
                unset($config[$key]);
155
            }
156
        }
157
158 46
        $this->methodsAndProperties = $methodsAndProperties;
159 46
    }
160
161
    /**
162
     * @psalm-return class-string
163
     */
164 43
    public function getClass(): string
165
    {
166 43
        return $this->class;
167
    }
168
169 40
    public function getConstructorArguments(): array
170
    {
171 40
        return $this->constructorArguments;
172
    }
173
174
    /**
175
     * @psalm-return array<string, MethodOrPropertyItem>
176
     */
177 37
    public function getMethodsAndProperties(): array
178
    {
179 37
        return $this->methodsAndProperties;
180
    }
181
182
    /**
183
     * @throws NotInstantiableException
184
     * @throws InvalidConfigException
185
     */
186 42
    public function resolve(ContainerInterface $container): object
187
    {
188 42
        return ArrayDefinitionBuilder::getInstance()->build($container, $this);
189
    }
190
191 4
    public function merge(self $other): self
192
    {
193 4
        $new = clone $this;
194 4
        $new->class = $other->class;
195 4
        $new->constructorArguments = $this->mergeArguments($this->constructorArguments, $other->constructorArguments);
196
197 4
        $methodsAndProperties = $this->methodsAndProperties;
198 4
        foreach ($other->methodsAndProperties as $key => $item) {
199 2
            if ($item[0] === ArrayDefinitionBuilder::PROPERTY) {
200 1
                $methodsAndProperties[$key] = $item;
201 2
            } elseif ($item[0] === ArrayDefinitionBuilder::METHOD) {
202
                /** @psalm-suppress MixedArgument */
203 2
                $methodsAndProperties[$key] = [
204 2
                    $item[0],
205 2
                    $item[1],
206 2
                    isset($methodsAndProperties[$key])
207 1
                        ? $this->mergeArguments($methodsAndProperties[$key][2], $item[2])
208 2
                        : $item[2],
209
                ];
210
            }
211
        }
212 4
        $new->methodsAndProperties = $methodsAndProperties;
213
214 4
        return $new;
215
    }
216
217 4
    private function mergeArguments(array $selfArguments, array $otherArguments): array
218
    {
219
        /** @var mixed $argument */
220 4
        foreach ($otherArguments as $name => $argument) {
221
            /** @var mixed */
222 2
            $selfArguments[$name] = $argument;
223
        }
224
225 4
        return $selfArguments;
226
    }
227
228
    /**
229
     * @param mixed $value
230
     */
231 2
    private function getType($value): string
232
    {
233 2
        return is_object($value) ? get_class($value) : gettype($value);
234
    }
235
}
236