Passed
Pull Request — master (#8)
by Sergei
02:40
created

DefinitionValidator   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 150
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 63
c 1
b 0
f 0
dl 0
loc 150
ccs 63
cts 63
cp 1
rs 10
wmc 30

5 Methods

Rating   Name   Duplication   Size   Complexity  
A throwInvalidArrayDefinitionKey() 0 19 3
A getType() 0 3 2
A isValidObject() 0 3 2
B validate() 0 24 7
C validateArrayDefinition() 0 71 16
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Definitions\Infrastructure;
6
7
use Yiisoft\Definitions\ArrayDefinition;
8
use Yiisoft\Definitions\Contract\DefinitionInterface;
9
use Yiisoft\Definitions\Contract\ReferenceInterface;
10
use Yiisoft\Definitions\Exception\InvalidConfigException;
11
12
use function get_class;
13
use function gettype;
14
use function is_array;
15
use function is_callable;
16
use function is_object;
17
use function is_string;
18
19
final class DefinitionValidator
20
{
21
    /**
22
     * @param mixed $definition
23
     *
24
     * @throws InvalidConfigException
25
     */
26 20
    public static function validate($definition, ?string $id = null): void
27
    {
28
        // Reference or ready object
29 20
        if (is_object($definition) && self::isValidObject($definition)) {
30 2
            return;
31
        }
32
33
        // Class
34 18
        if (is_string($definition) && $definition !== '') {
35 1
            return;
36
        }
37
38
        // Callable definition
39 17
        if (is_callable($definition, true)) {
40 1
            return;
41
        }
42
43
        // Array definition
44 16
        if (is_array($definition)) {
45 15
            self::validateArrayDefinition($definition, $id);
46 1
            return;
47
        }
48
49 1
        throw new InvalidConfigException('Invalid definition: ' . var_export($definition, true));
50
    }
51
52
    /**
53
     * @throws InvalidConfigException
54
     */
55 15
    private static function validateArrayDefinition(array $definition, ?string $id): void
56
    {
57 15
        foreach ($definition as $key => $value) {
58 14
            if (!is_string($key)) {
59 1
                throw new InvalidConfigException(
60 1
                    sprintf(
61 1
                        'Invalid definition: invalid key in array definition. Allow only string keys, got %d.',
62
                        $key,
63
                    ),
64
                );
65
            }
66
67
            // Class
68 14
            if ($key === ArrayDefinition::CLASS_NAME) {
69 14
                if (!is_string($value)) {
70 1
                    throw new InvalidConfigException(
71 1
                        sprintf(
72 1
                            'Invalid definition: invalid class name. Expected string, got %s.',
73 1
                            self::getType($value),
74
                        ),
75
                    );
76
                }
77 13
                if ($value === '') {
78 1
                    throw new InvalidConfigException('Invalid definition: empty class name.');
79
                }
80 12
                continue;
81
            }
82
83
            // Constructor arguments
84 10
            if ($key === ArrayDefinition::CONSTRUCTOR) {
85 9
                if (!is_array($value)) {
86 1
                    throw new InvalidConfigException(
87 1
                        sprintf(
88 1
                            'Invalid definition: incorrect constructor arguments. Expected array, got %s.',
89 1
                            self::getType($value)
90
                        )
91
                    );
92
                }
93
                /** @var mixed $argument */
94 8
                foreach ($value as $argument) {
95 8
                    if (is_object($argument) && !self::isValidObject($argument)) {
96 1
                        throw new InvalidConfigException(
97
                            'Only references are allowed in constructor arguments, a definition object was provided: ' .
98 1
                            var_export($argument, true)
99
                        );
100
                    }
101
                }
102 7
                continue;
103
            }
104
105
            // Methods and properties
106 8
            if (substr($key, -2) === '()') {
107 8
                if (!is_array($value)) {
108 1
                    throw new InvalidConfigException(
109 1
                        sprintf(
110 1
                            'Invalid definition: incorrect method arguments. Expected array, got %s.',
111 1
                            self::getType($value)
112
                        )
113
                    );
114
                }
115 7
                continue;
116
            }
117 7
            if (strncmp($key, '$', 1) === 0) {
118 7
                continue;
119
            }
120
121 7
            self::throwInvalidArrayDefinitionKey($key);
122
        }
123
124 2
        if ($id === null && !isset($definition[ArrayDefinition::CLASS_NAME])) {
125 1
            throw new InvalidConfigException('Invalid definition: no class name specified.');
126
        }
127 1
    }
128
129
    /**
130
     * @throws InvalidConfigException
131
     */
132 7
    private static function throwInvalidArrayDefinitionKey(string $key): void
133
    {
134 7
        $preparedKey = trim(strtr($key, [
135 7
            '()' => '',
136
            '$' => '',
137
        ]));
138
139 7
        if ($preparedKey === '' || !preg_match('/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$/', $preparedKey)) {
140 2
            throw new InvalidConfigException(
141 2
                sprintf('Invalid definition: key "%s" is not allowed.', $key)
142
            );
143
        }
144
145 5
        throw new InvalidConfigException(
146 5
            sprintf(
147 5
                'Invalid definition: key "%s" is not allowed. Did you mean "%s()" or "$%s"?',
148
                $key,
149
                $preparedKey,
150
                $preparedKey
151
            )
152
        );
153
    }
154
155
    /**
156
     * Deny DefinitionInterface, exclude ReferenceInterface
157
     */
158 3
    private static function isValidObject(object $value): bool
159
    {
160 3
        return !($value instanceof DefinitionInterface) || $value instanceof ReferenceInterface;
161
    }
162
163
    /**
164
     * @param mixed $value
165
     */
166 3
    private static function getType($value): string
167
    {
168 3
        return is_object($value) ? get_class($value) : gettype($value);
169
    }
170
}
171