Passed
Push — master ( 8afadc...1cb41c )
by Alexander
03:09 queued 54s
created

DefinitionValidator::isValidObject()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

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