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

nextras / orm / 27441222947

12 Jun 2026 08:29PM UTC coverage: 91.915% (-0.2%) from 92.162%
27441222947

Pull #810

github

web-flow
Merge 191dbb3fe into 7a20304be
Pull Request #810: Fix DbalCollection::getQueryBuilder()

61 of 66 new or added lines in 3 files covered. (92.42%)

6 existing lines in 3 files now uncovered.

4263 of 4638 relevant lines covered (91.91%)

1.81 hits per line

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

96.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();
2✔
24
        }
2✔
25

26

27
        /**
28
         * @return list<string>
29
         */
30
        public function matchModifiers(string $input): array
31
        {
32
                preg_match_all('#
2✔
33
                        \{(
34
                                (?:
35
                                        ' . TokenLexer::RE_STRING . ' |
2✔
36
                                        [^}]
37
                                )++
38
                        )}#x', $input, $matches);
39
                return $matches[1];
2✔
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);
2✔
51
                return [
52
                        $name = $this->processName($iterator),
2✔
53
                        $this->processArgs($iterator, $reflectionClass, $name, false),
2✔
54
                ];
55
        }
56

57

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

74
                return $currentToken->value;
2✔
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 = [];
2✔
90
                while (($currentToken = $iterator->nextToken()) !== null) {
2✔
91
                        $type = $currentToken->type;
2✔
92

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

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

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

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

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

152
                return $result;
2✔
153
        }
154

155

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

170

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

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

© 2026 Coveralls, Inc