Passed
Pull Request — master (#85)
by Alexander
02:14
created

getConstructorArgumentsFromConfig()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

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