Test Failed
Pull Request — master (#85)
by Sergei
02:15
created

getConstructorArgumentsFromConfig()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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