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

ArrayDefinition::getMeta()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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