Passed
Pull Request — master (#85)
by Sergei
02:10
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
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 46
        $this->setConstructorArguments($config);
55 45
        $this->setMethodsAndProperties($config);
56
57 44
        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 42
    }
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
        $this->class = $class;
94 46
    }
95
96
    /**
97
     * @psalm-param array<string, mixed> $config
98
     *
99
     * @throws InvalidConfigException
100
     */
101 46
    private function setConstructorArguments(array &$config): void
102
    {
103 46
        if (!isset($config[self::CONSTRUCTOR])) {
104 34
            $this->constructorArguments = [];
105 34
            return;
106
        }
107
108 14
        $arguments = $config[self::CONSTRUCTOR];
109 14
        unset($config[self::CONSTRUCTOR]);
110
111 14
        if (!is_array($arguments)) {
112 1
            throw new InvalidConfigException(
113 1
                sprintf(
114 1
                    'Invalid definition: incorrect constructor arguments. Expected array, got %s.',
115 1
                    $this->getType($arguments)
116
                )
117
            );
118
        }
119
120 13
        $this->constructorArguments = $arguments;
121 13
    }
122
123
    /**
124
     * @psalm-param array<string, mixed> $config
125
     *
126
     * @throws InvalidConfigException
127
     */
128 45
    private function setMethodsAndProperties(array &$config): void
129
    {
130 45
        $methodsAndProperties = [];
131
132 45
        foreach ($config as $key => $value) {
133 16
            if (substr($key, -2) === '()') {
134 12
                if (!is_array($value)) {
135 1
                    throw new InvalidConfigException(
136 1
                        sprintf('Invalid definition: incorrect method arguments. Expected array, got %s.', $this->getType($value))
137
                    );
138
                }
139
140
                /** @var string $methodName */
141 11
                $methodName = substr($key, 0, -2);
142
143 11
                $methodsAndProperties[] = [ArrayDefinitionBuilder::METHOD, $methodName, $value];
144 11
                unset($config[$key]);
145 5
            } elseif (strncmp($key, '$', 1) === 0) {
146
                /** @var string $propertyName */
147 3
                $propertyName = substr($key, 1);
148
149 3
                $methodsAndProperties[$key] = [ArrayDefinitionBuilder::PROPERTY, $propertyName, $value];
150 3
                unset($config[$key]);
151
            }
152
        }
153
154 44
        $this->methodsAndProperties = $methodsAndProperties;
155 44
    }
156
157
    /**
158
     * @psalm-return class-string
159
     */
160 42
    public function getClass(): string
161
    {
162 42
        return $this->class;
163
    }
164
165 39
    public function getConstructorArguments(): array
166
    {
167 39
        return $this->constructorArguments;
168
    }
169
170 36
    public function getMethodsAndProperties(): array
171
    {
172 36
        return $this->methodsAndProperties;
173
    }
174
175
    /**
176
     * @throws NotInstantiableException
177
     * @throws InvalidConfigException
178
     */
179 42
    public function resolve(ContainerInterface $container): object
180
    {
181 42
        return ArrayDefinitionBuilder::getInstance()->build($container, $this);
182
    }
183
184 2
    public function merge(self $other): self
185
    {
186 2
        $new = clone $this;
187 2
        $new->class = $other->class;
188 2
        $new->constructorArguments = $this->mergeArguments($this->constructorArguments, $other->constructorArguments);
189
190 2
        $methodsAndProperties = $this->methodsAndProperties;
191 2
        foreach ($other->methodsAndProperties as $key => $item) {
192 1
            if ($item[0] === ArrayDefinitionBuilder::PROPERTY) {
193
                $methodsAndProperties[$key] = $item;
194 1
            } elseif ($item[0] === ArrayDefinitionBuilder::METHOD) {
195 1
                $methodsAndProperties[$key] = [
196 1
                    $item[0],
197 1
                    $item[1],
198 1
                    isset($methodsAndProperties[$key])
199 1
                        ? $this->mergeArguments($methodsAndProperties[$key][2], $item[2])
200 1
                        : $item[2],
201
                ];
202
            }
203
        }
204 2
        $new->methodsAndProperties = $methodsAndProperties;
205
206 2
        return $new;
207
    }
208
209 2
    private function mergeArguments(array $selfArguments, array $otherArguments): array
210
    {
211
        /** @var mixed $argument */
212 2
        foreach ($otherArguments as $name => $argument) {
213
            /** @var mixed */
214 1
            $selfArguments[$name] = $argument;
215
        }
216
217 2
        return $selfArguments;
218
    }
219
220
    /**
221
     * @param mixed $value
222
     */
223 2
    private function getType($value): string
224
    {
225 2
        return is_object($value) ? get_class($value) : gettype($value);
226
    }
227
}
228