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

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