Passed
Pull Request — master (#78)
by Dmitriy
02:32
created

Normalizer   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 170
Duplicated Lines 0 %

Test Coverage

Coverage 57.38%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
wmc 31
eloc 58
c 3
b 1
f 0
dl 0
loc 170
ccs 35
cts 61
cp 0.5738
rs 9.92

4 Methods

Rating   Name   Duplication   Size   Complexity  
A getType() 0 3 2
B parse() 0 38 10
B validate() 0 27 8
B normalize() 0 38 11
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Factory\Definition;
6
7
use Yiisoft\Factory\Exception\InvalidConfigException;
8
9
use function array_key_exists;
10
use function is_array;
11
use function is_callable;
12
use function is_object;
13
use function is_string;
14
15
/**
16
 * Class Definition represents a definition in a container
17
 */
18
class Normalizer
19
{
20
    private const DEFINITION_META = 'definition';
21
    /**
22
     * Definition may be defined multiple ways.
23
     * Interface name as string:
24
     *
25
     * ```php
26
     * $container->set('interface_name', EngineInterface::class);
27
     * ```
28
     *
29
     * A closure:
30
     *
31
     * ```php
32
     * $container->set('closure', function($container) {
33
     *     return new MyClass($container->get('db'));
34
     * });
35
     * ```
36
     *
37
     * A callable array:
38
     *
39
     * ```php
40
     * $container->set('static_call', [MyClass::class, 'create']);
41
     * ```
42
     *
43
     * A definition array:
44
     *
45
     * ```php
46
     * $container->set('full_definition', [
47
     *     'class' => EngineMarkOne::class,
48
     *     '__construct()' => [42],
49
     *     '@argName' => 'value',
50
     *     'setX()' => [42],
51
     * ]);
52
     * ```
53
     *
54
     * @param mixed $definition
55
     *
56
     * @throws InvalidConfigException
57
     */
58 25
    public static function normalize($definition, string $id = null, array $constructorArguments = [], array $allowedMeta = []): DefinitionInterface
59
    {
60 25
        if ($definition instanceof DefinitionInterface) {
61 1
            return $definition;
62
        }
63
64 25
        if (is_string($definition)) {
65 18
            if (empty($definition)) {
66
                throw new InvalidConfigException('Invalid definition: empty string.');
67
            }
68 18
            if ($id === $definition || (!empty($constructorArguments) && class_exists($definition))) {
69
                /** @psalm-var class-string $definition */
70 6
                return new ArrayDefinition([
71 6
                    ArrayDefinition::CLASS_NAME => $definition,
72 6
                    ArrayDefinition::CONSTRUCTOR => $constructorArguments,
73
                ]);
74
            }
75 12
            return Reference::to($definition);
76
        }
77
78 11
        if (is_callable($definition, true)) {
79 3
            return new CallableDefinition($definition);
80
        }
81
82 8
        if (is_array($definition)) {
83 7
            $config = $definition;
84 7
            if (!array_key_exists(ArrayDefinition::CLASS_NAME, $config)) {
85
                $config[ArrayDefinition::CLASS_NAME] = $id;
86
            }
87
            /** @psalm-suppress ArgumentTypeCoercion */
88 7
            return new ArrayDefinition($config, $allowedMeta);
89
        }
90
91 1
        if (is_object($definition)) {
92 1
            return new ValueDefinition($definition);
93
        }
94
95
        throw new InvalidConfigException('Invalid definition:' . var_export($definition, true));
96
    }
97
98
    /**
99
     * Validates definition for correctness.
100
     *
101
     * @param mixed $definition {@see normalize()}
102
     *
103
     * @throws InvalidConfigException
104
     */
105
    public static function validate($definition, bool $throw = true): bool
106
    {
107
        if ($definition instanceof DefinitionInterface) {
108
            return true;
109
        }
110
111
        if (is_string($definition) && !empty($definition)) {
112
            return true;
113
        }
114
115
        if (is_callable($definition)) {
116
            return true;
117
        }
118
119
        if (is_array($definition)) {
120
            return true;
121
        }
122
123
        if (is_object($definition)) {
124
            return true;
125
        }
126
127
        if ($throw) {
128
            throw new InvalidConfigException('Invalid definition:' . var_export($definition, true));
129
        }
130
131
        return false;
132
    }
133
134
    /**
135
     * Validates definition for correctness.
136
     *
137
     * @param mixed $definition
138
     * @param array $allowedMeta
139
     *
140
     * @throws InvalidConfigException
141
     */
142 49
    public static function parse($definition, array $allowedMeta): array
143
    {
144 49
        if (!is_array($definition)) {
145
            return [$definition, []];
146
        }
147
148 49
        $meta = [];
149 49
        if (isset($definition[self::DEFINITION_META])) {
150
            $newDefinition = $definition[self::DEFINITION_META];
151
            unset($definition[self::DEFINITION_META]);
152
            $meta = array_filter($definition, function ($key) use ($allowedMeta) {
153
                return in_array($key, $allowedMeta);
154
            }, ARRAY_FILTER_USE_KEY);
155
            $definition = $newDefinition;
156
        }
157
158 49
        foreach ($definition as $key => $value) {
159
            // Method.
160 48
            if ($key === ArrayDefinition::CLASS_NAME || $key === ArrayDefinition::CONSTRUCTOR) {
161 48
                continue;
162
            }
163 16
            if (substr($key, -2) === '()') {
164 12
                if (!is_array($value)) {
165 1
                    throw new InvalidConfigException(
166 12
                        sprintf('Invalid definition: incorrect method arguments. Expected array, got %s.', self::getType($value))
167
                    );
168
                }
169
                // Not property = meta.
170 5
            } elseif (substr($key, 0, 1) !== '@') {
171 2
                if (!in_array($key, $allowedMeta, true)) {
172 2
                    throw new InvalidConfigException(sprintf('Invalid definition: metadata "%s" is not allowed. Did you mean "%s()" or "@%s"?', $key, $key, $key));
173
                }
174
                $meta[$key] = $value;
175
                unset($definition[$key]);
176
            }
177
        }
178
179 46
        return [$definition, $meta];
180
    }
181
182
    /**
183
     * @param mixed $value
184
     */
185 1
    private static function getType($value): string
186
    {
187 1
        return is_object($value) ? get_class($value) : gettype($value);
188
    }
189
}
190