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