Passed
Pull Request — master (#78)
by Alexander
02:12
created

ArrayDefinition::resolve()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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 is_array;
16
use function is_object;
17
use function is_string;
18
19
/**
20
 * Builds object by array config
21
 */
22
class ArrayDefinition implements DefinitionInterface
23
{
24
    public const CLASS_NAME = 'class';
25
    public const CONSTRUCTOR = '__construct()';
26
27
    /**
28
     * @psalm-var class-string
29
     */
30
    private string $class;
31
    private array $constructorArguments;
32
33
    /**
34
     * @psalm-var array<string, mixed|array>
35
     */
36
    private array $methodsAndProperties = [];
37
38
    /**
39
     * @psalm-var array<string, mixed>
40
     */
41
    private array $meta = [];
42
43
    /**
44
     * @param array $config Container entry config.
45
     * @param string[] $allowedMeta Allowed metadata keys.
46
     *
47
     * @psalm-param array{
48
     *   class: class-string,
49
     *   constructor?: array,
50
     *   callMethods?: array,
51
     *   setProperties?: array,
52
     * } $config
53
     *
54
     * @throws InvalidConfigException
55
     */
56 49
    public function __construct(array $config, array $allowedMeta = [])
57
    {
58 49
        $this->setClass($config);
59 46
        unset($config[self::CLASS_NAME]);
60 46
        $this->setConstructorArguments($config);
61 45
        unset($config[self::CONSTRUCTOR]);
62
63 45
        foreach ($config as $key => $value) {
64
            // Method.
65 16
            if (substr($key, -2) === '()') {
66 12
                if (!is_array($value)) {
67 1
                    throw new InvalidConfigException(
68 12
                        sprintf('Invalid definition: incorrect method arguments. Expected array, got %s.', $this->getType($value))
69
                    );
70
                }
71
                // Not property = meta.
72 5
            } elseif (substr($key, 0, 1) !== '@') {
73 2
                if (!in_array($key, $allowedMeta, true)) {
74 2
                    throw new InvalidConfigException(sprintf('Invalid definition: metadata "%s" is not allowed. Did you mean "%s()" or "@%s"?', $key, $key, $key));
75
                }
76
                $this->meta[$key] = $value;
77
                unset($config[$key]);
78
            }
79
        }
80
81 42
        $this->methodsAndProperties = $config;
82 42
    }
83
84
    /**
85
     * @throws InvalidConfigException
86
     */
87 49
    private function setClass(array $config): void
88
    {
89 49
        if (!array_key_exists(self::CLASS_NAME, $config)) {
90 1
            throw new InvalidConfigException('Invalid definition: no class name specified.');
91
        }
92
93
        /** @var mixed */
94 48
        $class = $config[self::CLASS_NAME];
95
96 48
        if (!is_string($class)) {
97 1
            throw new InvalidConfigException(sprintf('Invalid definition: invalid class name "%s".', (string)$class));
98
        }
99
100 47
        if ($class === '') {
101 1
            throw new InvalidConfigException('Invalid definition: empty class name.');
102
        }
103
104 46
        $this->class = $class;
105 46
    }
106
107
    /**
108
     * @throws InvalidConfigException
109
     */
110 46
    private function setConstructorArguments(array $config): void
111
    {
112 46
        $arguments = $config[self::CONSTRUCTOR] ?? [];
113
114 46
        if (!is_array($arguments)) {
115 1
            throw new InvalidConfigException(
116 1
                sprintf(
117 1
                    'Invalid definition: incorrect constructor arguments. Expected array, got %s.',
118 1
                    $this->getType($arguments)
119
                )
120
            );
121
        }
122
123 45
        $this->constructorArguments = $arguments;
124 45
    }
125
126
    /**
127
     * @psalm-return class-string
128
     */
129 42
    public function getClass(): string
130
    {
131 42
        return $this->class;
132
    }
133
134 39
    public function getConstructorArguments(): array
135
    {
136 39
        return $this->constructorArguments;
137
    }
138
139 36
    public function getMethodsAndProperties(): array
140
    {
141 36
        return $this->methodsAndProperties;
142
    }
143
144
    /**
145
     * @throws NotInstantiableException
146
     * @throws InvalidConfigException
147
     */
148 42
    public function resolve(ContainerInterface $container): object
149
    {
150 42
        return ArrayDefinitionBuilder::getInstance()->build($container, $this);
151
    }
152
153 2
    public function merge(self $other): self
154
    {
155 2
        $methodsAndProperties = $this->getMethodsAndProperties();
156
157 2
        foreach ($other->getMethodsAndProperties() as $name => $arguments) {
158 1
            $methodsAndProperties[$name] = isset($methodsAndProperties[$name])
159 1
                ? $this->mergeArguments($methodsAndProperties[$name], $arguments)
160
                : $arguments;
161
        }
162
163 2
        return new self(array_merge([
164 2
            self::CLASS_NAME => $other->getClass(),
165 2
            self::CONSTRUCTOR => $this->mergeArguments(
166 2
                $this->getConstructorArguments(),
167 2
                $other->getConstructorArguments()
168
            ),
169
        ], $methodsAndProperties));
170
    }
171
172 2
    private function mergeArguments(array $selfArguments, array $otherArguments): array
173
    {
174
        /** @var mixed $argument */
175 2
        foreach ($otherArguments as $name => $argument) {
176
            /** @var mixed */
177 1
            $selfArguments[$name] = $argument;
178
        }
179
180 2
        return $selfArguments;
181
    }
182
183
    /**
184
     * @param mixed $value
185
     */
186 2
    private function getType($value): string
187
    {
188 2
        return is_object($value) ? get_class($value) : gettype($value);
189
    }
190
}
191