• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nextras / orm / 40780907175

28 Oct 2026 10:33PM UTC coverage: 92.01% (+0.01%) from 91.998%
40780907175

Pull #842

github

web-flow
Merge 6ce4bf857 into 10fe91984
Pull Request #842: Setup DressCode

253 of 306 new or added lines in 64 files covered. (82.68%)

13 existing lines in 9 files now uncovered.

4399 of 4781 relevant lines covered (92.01%)

5.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.0
/src/Entity/Reflection/ModifierParser.php
1
<?php declare(strict_types = 1);
2

3
namespace Nextras\Orm\Entity\Reflection;
4

5

6
use BackedEnum;
7
use Nette\Utils\Reflection;
8
use Nextras\Orm\Entity\Reflection\Parser\Token;
9
use Nextras\Orm\Entity\Reflection\Parser\TokenLexer;
10
use Nextras\Orm\Entity\Reflection\Parser\TokenStream;
11
use Nextras\Orm\Exception\InvalidStateException;
12
use ReflectionClass;
13
use ReflectionEnum;
14

15

16
class ModifierParser
17
{
18
        private TokenLexer $tokenLexer;
19

20

21
        public function __construct()
22
        {
23
                $this->tokenLexer = new TokenLexer();
6 ✔
24
        }
6 ✔
25

26

27
        /**
28
         * @return list<string>
29
         */
30
        public function matchModifiers(string $input): array
31
        {
32
                preg_match_all('#
6 ✔
33
                        \{(
34
                                (?:
35
                                        ' . TokenLexer::RE_STRING . ' |
6 ✔
36
                                        [^}]
37
                                )++
38
                        )}#x', $input, $matches);
39
                return $matches[1];
6 ✔
40
        }
41

42

43
        /**
44
         * @param ReflectionClass<covariant object> $reflectionClass
45
         * @return array{string, array<int|string, mixed>}
46
         * @throws InvalidModifierDefinitionException
47
         */
48
        public function parse(string $string, ReflectionClass $reflectionClass): array
49
        {
50
                $iterator = $this->tokenLexer->lex($string);
6 ✔
51
                return [
52
                        $name = $this->processName($iterator),
6 ✔
53
                        $this->processArgs($iterator, $reflectionClass, $name, false),
6 ✔
54
                ];
55
        }
56

57

58
        private function processName(TokenStream $iterator): string
59
        {
60
                $iterator->position++;
6 ✔
61
                $currentToken = $iterator->currentToken();
6 ✔
62
                if ($currentToken === null) {
6 ✔
NEW
63
                        throw new InvalidModifierDefinitionException('Modifier does not have a name.');
×
64
                }
65
                if ($currentToken->type !== Token::KEYWORD) {
6 ✔
66
                        throw new InvalidModifierDefinitionException('Modifier does not have a name.');
6 ✔
67
                } elseif (isset($iterator->tokens[$iterator->position + 1])) {
6 ✔
68
                        $nextToken = $iterator->tokens[$iterator->position + 1];
6 ✔
69
                        if ($nextToken->type === Token::SEPARATOR) {
6 ✔
70
                                throw new InvalidModifierDefinitionException("After the {{$currentToken->value}}'s modifier name cannot be a comma separator.");
6 ✔
71
                        }
72
                }
73

74
                return $currentToken->value;
6 ✔
75
        }
76

77

78
        /**
79
         * @param ReflectionClass<covariant object> $reflectionClass
80
         * @return array<int|string, mixed>
81
         */
82
        private function processArgs(
83
                TokenStream $iterator,
84
                ReflectionClass $reflectionClass,
85
                string $modifierName,
86
                bool $inArray,
87
        ): array
88
        {
89
                $result = [];
6 ✔
90
                while (($currentToken = $iterator->nextToken()) !== null) {
6 ✔
91
                        $type = $currentToken->type;
6 ✔
92

93
                        if ($type === Token::RBRACKET) {
6 ✔
94
                                if ($inArray) {
6 ✔
95
                                        return $result;
6 ✔
96
                                } else {
97
                                        throw new InvalidModifierDefinitionException("Modifier {{$modifierName}} mismatches brackets.");
6 ✔
98
                                }
99
                        } elseif ($type === Token::STRING || $type === Token::KEYWORD) {
6 ✔
100
                                $iterator->position++;
6 ✔
101
                                $nextToken = $iterator->currentToken();
6 ✔
102
                                $nextTokenType = $nextToken?->type;
6 ✔
103

104
                                if ($nextTokenType === Token::EQUAL) {
6 ✔
105
                                        $iterator->position++;
6 ✔
106
                                        $nextToken = $iterator->currentToken();
6 ✔
107
                                        $nextTokenType = $nextToken?->type;
6 ✔
108

109
                                        if ($nextTokenType === Token::LBRACKET) {
6 ✔
110
                                                $value = $this->processValue($currentToken, $reflectionClass);
6 ✔
111
                                                assert(!is_array($value));
112
                                                $result[$value] = $this->processArgs($iterator, $reflectionClass, $modifierName, true);
6 ✔
113
                                        } elseif ($nextTokenType === Token::STRING || $nextTokenType === Token::KEYWORD) {
6 ✔
114
                                                $value = $this->processValue($currentToken, $reflectionClass);
6 ✔
115
                                                assert(!is_array($value));
116
                                                $result[$value] = $this->processValue($nextToken, $reflectionClass);
6 ✔
117
                                        } elseif ($nextTokenType !== null) {
×
118
                                                throw new InvalidModifierDefinitionException("Modifier {{$modifierName}} has invalid token after =.");
2 ✔
119
                                        }
120
                                } else {
121
                                        $iterator->position--;
6 ✔
122
                                        $value = $this->processValue($currentToken, $reflectionClass);
6 ✔
123
                                        if (is_array($value)) {
6 ✔
124
                                                foreach ($value as $subValue) {
6 ✔
125
                                                        $result[] = $subValue;
6 ✔
126
                                                }
127
                                        } else {
128
                                                $result[] = $value;
6 ✔
129
                                        }
130
                                }
131
                        } elseif ($type === Token::LBRACKET) {
6 ✔
132
                                $result[] = $this->processArgs($iterator, $reflectionClass, $modifierName, true);
6 ✔
133
                        } else {
134
                                throw new InvalidModifierDefinitionException("Modifier {{$modifierName}} has invalid token, expected string, keyword, or array.");
6 ✔
135
                        }
136

137
                        $iterator->position++;
6 ✔
138
                        $currentToken2 = $iterator->currentToken();
6 ✔
139
                        $type = $currentToken2?->type;
6 ✔
140
                        if ($type === Token::RBRACKET && $inArray) {
6 ✔
141
                                return $result;
6 ✔
142
                        } elseif ($type !== null && $type !== Token::SEPARATOR) {
6 ✔
143
                                throw new InvalidModifierDefinitionException("Modifier {{$modifierName}} misses argument separator.");
6 ✔
144
                        }
145
                }
146

147
                if ($inArray) {
6 ✔
148
                        throw new InvalidModifierDefinitionException("Modifier {{$modifierName}} has unclosed array argument.");
6 ✔
149
                }
150

151
                return $result;
6 ✔
152
        }
153

154

155
        /**
156
         * @param ReflectionClass<covariant object> $reflectionClass
157
         */
158
        private function processValue(Token $token, ReflectionClass $reflectionClass): mixed
159
        {
160
                if ($token->type === Token::STRING) {
6 ✔
161
                        return stripslashes(substr($token->value, 1, -1));
6 ✔
162
                } elseif ($token->type === Token::KEYWORD) {
6 ✔
163
                        return $this->processKeyword($token->value, $reflectionClass);
6 ✔
164
                } else {
165
                        throw new InvalidStateException();
×
166
                }
167
        }
168

169

170
        /**
171
         * @param ReflectionClass<covariant object> $reflectionClass
172
         */
173
        private function processKeyword(string $value, ReflectionClass $reflectionClass): mixed
174
        {
175
                if (strcasecmp($value, 'true') === 0) {
6 ✔
176
                        return true;
6 ✔
177
                } elseif (strcasecmp($value, 'false') === 0) {
6 ✔
178
                        return false;
6 ✔
179
                } elseif (strcasecmp($value, 'null') === 0) {
6 ✔
180
                        return null;
6 ✔
181
                } elseif (is_numeric($value)) {
6 ✔
182
                        return $value * 1;
6 ✔
183
                } elseif (preg_match('#^[a-z0-9_\\\\]+::[a-z0-9_]*(\\*)?$#i', $value) === 1) {
6 ✔
184
                        [$className, $const] = explode('::', $value, 2);
6 ✔
185
                        if ($className === 'self' || $className === 'static') {
6 ✔
186
                                $reflection = $reflectionClass;
6 ✔
187
                        } else {
188
                                $className = Reflection::expandClassName($className, $reflectionClass); // @phpstan-ignore argument.type (https://github.com/phpstan/phpstan/issues/12459#issuecomment-2607123277)
6 ✔
189
                                assert(class_exists($className) || interface_exists($className));
190
                                $reflection = new ReflectionClass($className);
6 ✔
191
                        }
192

193
                        if ($reflection->isEnum() && is_subclass_of($className, BackedEnum::class)) {
6 ✔
194
                                return (new ReflectionEnum($className))->getCase($const)->getValue();
6 ✔
195
                        }
196
                        $enum = [];
6 ✔
197
                        $constants = $reflection->getConstants();
6 ✔
198
                        if (str_contains($const, '*')) {
6 ✔
199
                                $prefix = rtrim($const, '*');
6 ✔
200
                                $prefixLength = strlen($prefix);
6 ✔
201
                                $count = 0;
6 ✔
202
                                foreach ($constants as $name => $constantValue) {
6 ✔
203
                                        if (substr($name, 0, $prefixLength) === $prefix) {
6 ✔
204
                                                $enum[] = $constantValue;
6 ✔
205
                                                $count++;
6 ✔
206
                                        }
207
                                }
208
                                if ($count === 0) {
6 ✔
209
                                        throw new InvalidModifierDefinitionException("No constant matches $reflection->name::$const pattern.");
6 ✔
210
                                }
211
                        } else {
212
                                if (!array_key_exists($const, $constants)) {
6 ✔
213
                                        throw new InvalidModifierDefinitionException("Constant $reflection->name::$const does not exist.");
6 ✔
214
                                }
215
                                $value = $reflection->getConstant($const);
6 ✔
216
                                $enum[] = $value;
6 ✔
217
                        }
218
                        return $enum;
6 ✔
219
                } else {
220
                        return $value;
6 ✔
221
                }
222
        }
223
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc