Passed
Pull Request — master (#88)
by Alexander
02:22
created

Normalizer::normalize()   B

Complexity

Conditions 11
Paths 9

Size

Total Lines 42
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 11.307

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 21
c 2
b 0
f 0
dl 0
loc 42
ccs 19
cts 22
cp 0.8636
rs 7.3166
cc 11
nc 9
nop 3
crap 11.307

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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