PluginProbe ʕ •ᴥ•ʔ
MailPoet – Newsletters, Email Marketing, and Automation / 5.36.0
MailPoet – Newsletters, Email Marketing, and Automation v5.36.0
5.36.0 5.35.1 5.35.0 5.34.3 5.34.2 5.34.1 5.34.0 5.33.1 5.33.0 5.32.0 5.31.0 5.30.0 5.29.0 5.28.1 5.28.0 5.27.0 5.26.0 5.26.1 5.25.0 5.24.0 4.43.0 4.43.1 4.44.0 4.44.1 4.45.0 4.46.0 4.47.0 4.48.0 4.48.1 4.48.2 4.49.0 4.49.1 4.5.0 4.5.1 4.5.2 4.50.0 4.50.1 4.51.0 4.51.1 4.51.2 4.52.0 4.53.0 4.54.0 4.55.0 4.56.0 4.57.0 4.58.0 4.58.1 4.58.2 4.6.0 4.6.1 4.6.2 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 5.0.0 5.0.1 5.0.2 5.1.0 5.1.1 5.10.0 5.10.1 5.11.0 5.12.0 5.12.1 5.12.10 5.12.11 5.12.12 5.12.13 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.12.7 5.12.8 5.12.9 5.13.0 5.13.1 5.13.2 5.14.0 5.14.1 5.14.2 5.14.3 5.15.0 5.15.1 5.16.0 5.16.1 5.16.2 5.16.3 5.16.4 5.17.0 5.17.1 5.17.2 5.17.3 5.17.4 5.17.5 5.17.6 5.18.0 5.19.0 5.2.0 5.2.1 5.2.2 5.2.3 5.20.0 5.21.0 5.21.1 5.21.2 5.21.3 5.22.0 5.22.1 5.22.2 5.22.3 5.22.4 5.23.0 5.23.1 5.23.2 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.3.7 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 5.5.2 5.6.0 5.6.1 5.6.2 5.6.3 5.6.4 5.7.0 5.7.1 5.8.0 5.8.1 5.9.0 3.0.0-beta.15 3.7.1 3.0.0-beta.16 3.7.2 3.0.0-beta.17 3.7.3 3.0.0-beta.18 3.7.4 3.0.0-beta.19 3.7.5 3.0.0-beta.2 3.7.6 3.0.0-beta.20 3.7.8 3.0.0-beta.21 3.70.0 3.0.0-beta.22 3.71.0 3.0.0-beta.23 3.71.1 3.0.0-beta.23.1 3.71.2 3.0.0-beta.23.2 3.71.3 3.0.0-beta.24 3.72.0 3.0.0-beta.25 3.73.0 3.0.0-beta.26 3.73.1 3.0.0-beta.27 3.73.2 3.0.0-beta.28 3.74.0 3.0.0-beta.29 3.74.1 3.0.0-beta.3 3.74.2 3.0.0-beta.30 3.74.3 3.0.0-beta.31 3.75.0 3.0.0-beta.32 3.75.1 3.0.0-beta.33 3.76.0 3.0.0-beta.33.1 3.77.0 3.0.0-beta.34.0.0 3.77.1 3.0.0-beta.36.0.0 3.78.0 3.0.0-beta.36.0.1 3.79.0 3.0.0-beta.36.2.0 3.8 3.0.0-beta.36.3.0 3.8.1 3.0.0-beta.36.3.1 3.8.2 3.0.0-beta.37.0.0 3.8.3 3.0.0-beta.4 3.8.4 3.0.0-beta.5 3.8.5 3.0.0-beta.6 3.8.6 3.0.0-beta.7 3.80.0 3.0.0-beta.7.1 3.81.0 3.0.0-beta.8 3.82.0 3.0.0-beta.9 3.83.0 3.0.0-rc.1.0.0 3.84.0 3.0.0-rc.1.0.1 3.84.1 3.0.0-rc.1.0.2 3.85.0 3.0.0-rc.1.0.3 3.85.1 3.0.0-rc.1.0.4 3.86.0 3.0.0-rc.2.0.0 3.87.0 3.0.0-rc.2.0.1 3.87.1 3.0.0-rc.2.0.2 3.87.2 3.0.0-rc.2.0.3 3.88.0 3.0.1 3.88.1 3.0.2 3.88.2 3.0.3 3.89.0 3.0.4 3.89.1 3.0.5 3.89.2 3.0.6 3.89.3 3.0.7 3.89.4 3.0.8 3.9.0 3.0.9 3.9.1 3.1.0 3.90.0 3.10 3.90.1 3.10.1 3.90.2 3.100.0 3.91.0 3.100.1 3.91.1 3.100.2 3.92.0 3.101.0 3.92.1 3.101.1 3.93.0 3.102.0 3.93.1 3.102.1 3.94.0 3.103.0 3.95.0 3.103.1 3.95.1 3.11.0 3.96.0 3.11.1 3.96.1 3.11.2 3.97.0 3.11.3 3.98.0 3.11.4 3.98.1 3.11.5 3.99.0 3.12.0 3.99.1 3.12.1 4.0.0 3.13.0 4.0.1 3.14.0 4.1.0 3.14.1 4.1.1 3.15.0 4.10.0 3.16.0 4.11.0 3.16.1 4.11.1 3.16.2 4.12.0 3.16.3 4.12.1 3.17.0 4.12.2 3.17.1 4.13.0 3.17.2 4.14.0 3.18.0 4.15.0 3.18.1 4.16.0 3.18.2 4.17.0 3.19.0 4.17.1 3.19.1 4.18.0 3.19.2 4.18.1 3.19.3 4.19.0 3.2.0 4.2.0 3.2.1 4.20.0 3.2.2 4.20.1 3.2.3 4.20.2 3.2.4 4.21.0 3.2.5 4.22.0 3.20.0 4.22.1 3.21.0 4.22.2 3.21.1 4.23.0 3.22.0 4.24.0 3.23.0 4.25.0 3.23.1 4.26.0 3.23.2 4.26.1 3.24.0 4.27.0 3.25.0 4.28.0 3.25.1 4.29.0 3.26.0 4.3.0 3.26.1 4.3.1 3.27.0 4.30.0 3.28.0 4.31.0 3.29.0 4.31.1 3.3.0 4.32.0 3.3.1 4.33.0 3.3.2 4.34.0 3.3.3 4.35.0 3.3.4 4.35.1 3.3.5 4.36.0 3.3.6 4.37.0 3.30.0 4.38.0 3.31.0 4.39.0 3.31.1 4.4.0 3.32.0 4.40.0 3.32.1 4.41.0 3.32.2 4.41.1 3.33.0 4.41.2 3.34.0 4.41.3 3.34.1 4.42.0 3.34.2 4.42.1 3.34.3 3.34.4 3.35.0 3.35.1 3.35.3 3.35.4 3.36.0 3.37.0 3.37.1 3.37.2 3.37.3 3.38.0 3.38.1 3.39.0 3.39.1 3.39.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.40.0 3.40.1 3.41.0 3.41.1 3.41.2 3.42.0 3.42.1 3.42.2 3.42.3 3.43.0 3.43.1 3.44.0 3.45.0 3.45.1 3.46.0 3.46.1 3.46.10 3.46.11 3.46.12 3.46.13 3.46.14 3.46.2 3.46.3 3.46.4 3.46.5 3.46.6 3.46.7 3.46.8 3.46.9 3.47.0 3.47.1 3.47.10 3.47.11 3.47.2 3.47.3 3.47.5 3.47.6 3.47.7 3.47.9 3.48.0 3.48.1 3.49.0 3.49.1 3.5.0 3.5.1 3.50.0 3.51.0 3.51.1 3.51.2 3.52.0 3.53.0 3.54.0 3.54.1 3.54.2 3.54.3 3.55.0 3.55.1 3.56.0 3.56.1 3.56.2 3.57.0 3.57.1 3.58.0 3.59.0 3.59.1 3.59.2 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.60.0 3.60.1 3.60.10 3.60.11 3.60.12 3.60.2 3.60.3 3.60.4 3.60.6 3.60.7 3.60.8 3.60.9 3.61.0 3.62.0 3.62.1 3.63.0 3.64.0 3.64.1 3.64.2 3.64.3 3.65.0 trunk 3.65.1 3.0.0 3.66.0 3.0.0-beta.1 3.67.0 3.0.0-beta.10 3.67.1 3.0.0-beta.11 3.68.0 3.0.0-beta.12 3.69.0 3.0.0-beta.13 3.69.1 3.0.0-beta.14 3.7.0
mailpoet / vendor-prefixed / doctrine / orm / src / Query / Parser.php
mailpoet / vendor-prefixed / doctrine / orm / src / Query Last commit date
AST 9 months ago Exec 9 months ago Expr 9 months ago Filter 9 months ago Expr.php 9 months ago FilterCollection.php 9 months ago Lexer.php 9 months ago OutputWalker.php 9 months ago Parameter.php 9 months ago ParameterTypeInferer.php 9 months ago Parser.php 9 months ago ParserResult.php 9 months ago Printer.php 9 months ago QueryException.php 9 months ago QueryExpressionVisitor.php 9 months ago ResultSetMapping.php 9 months ago ResultSetMappingBuilder.php 9 months ago SqlOutputWalker.php 9 months ago SqlWalker.php 9 months ago TokenType.php 9 months ago TreeWalker.php 9 months ago TreeWalkerAdapter.php 9 months ago TreeWalkerChain.php 9 months ago TreeWalkerChainIterator.php 9 months ago index.php 9 months ago
Parser.php
2004 lines
1 <?php
2 declare (strict_types=1);
3 namespace MailPoetVendor\Doctrine\ORM\Query;
4 if (!defined('ABSPATH')) exit;
5 use MailPoetVendor\Doctrine\Common\Lexer\Token;
6 use MailPoetVendor\Doctrine\Deprecations\Deprecation;
7 use MailPoetVendor\Doctrine\ORM\EntityManagerInterface;
8 use MailPoetVendor\Doctrine\ORM\Mapping\ClassMetadata;
9 use MailPoetVendor\Doctrine\ORM\Query;
10 use MailPoetVendor\Doctrine\ORM\Query\AST\Functions;
11 use MailPoetVendor\Doctrine\ORM\Query\Exec\SqlFinalizer;
12 use LogicException;
13 use ReflectionClass;
14 use function array_intersect;
15 use function array_search;
16 use function assert;
17 use function class_exists;
18 use function count;
19 use function explode;
20 use function implode;
21 use function in_array;
22 use function interface_exists;
23 use function is_string;
24 use function sprintf;
25 use function str_contains;
26 use function strlen;
27 use function strpos;
28 use function strrpos;
29 use function strtolower;
30 use function substr;
31 class Parser
32 {
33 private static $stringFunctions = ['concat' => Functions\ConcatFunction::class, 'substring' => Functions\SubstringFunction::class, 'trim' => Functions\TrimFunction::class, 'lower' => Functions\LowerFunction::class, 'upper' => Functions\UpperFunction::class, 'identity' => Functions\IdentityFunction::class];
34 private static $numericFunctions = [
35 'length' => Functions\LengthFunction::class,
36 'locate' => Functions\LocateFunction::class,
37 'abs' => Functions\AbsFunction::class,
38 'sqrt' => Functions\SqrtFunction::class,
39 'mod' => Functions\ModFunction::class,
40 'size' => Functions\SizeFunction::class,
41 'date_diff' => Functions\DateDiffFunction::class,
42 'bit_and' => Functions\BitAndFunction::class,
43 'bit_or' => Functions\BitOrFunction::class,
44 // Aggregate functions
45 'min' => Functions\MinFunction::class,
46 'max' => Functions\MaxFunction::class,
47 'avg' => Functions\AvgFunction::class,
48 'sum' => Functions\SumFunction::class,
49 'count' => Functions\CountFunction::class,
50 ];
51 private static $datetimeFunctions = ['current_date' => Functions\CurrentDateFunction::class, 'current_time' => Functions\CurrentTimeFunction::class, 'current_timestamp' => Functions\CurrentTimestampFunction::class, 'date_add' => Functions\DateAddFunction::class, 'date_sub' => Functions\DateSubFunction::class];
52 private $deferredIdentificationVariables = [];
53 private $deferredPartialObjectExpressions = [];
54 private $deferredPathExpressions = [];
55 private $deferredResultVariables = [];
56 private $deferredNewObjectExpressions = [];
57 private $lexer;
58 private $parserResult;
59 private $em;
60 private $query;
61 private $queryComponents = [];
62 private $nestingLevel = 0;
63 private $customTreeWalkers = [];
64 private $customOutputWalker;
65 private $identVariableExpressions = [];
66 public function __construct(Query $query)
67 {
68 $this->query = $query;
69 $this->em = $query->getEntityManager();
70 $this->lexer = new Lexer((string) $query->getDQL());
71 $this->parserResult = new ParserResult();
72 }
73 public function setCustomOutputTreeWalker($className)
74 {
75 Deprecation::trigger('doctrine/orm', 'https://github.com/doctrine/orm/pull/11641', '%s is deprecated, set the output walker class with the \\Doctrine\\ORM\\Query::HINT_CUSTOM_OUTPUT_WALKER query hint instead', __METHOD__);
76 $this->customOutputWalker = $className;
77 }
78 public function addCustomTreeWalker($className)
79 {
80 $this->customTreeWalkers[] = $className;
81 }
82 public function getLexer()
83 {
84 return $this->lexer;
85 }
86 public function getParserResult()
87 {
88 return $this->parserResult;
89 }
90 public function getEntityManager()
91 {
92 return $this->em;
93 }
94 public function getAST()
95 {
96 // Parse & build AST
97 $AST = $this->QueryLanguage();
98 // Process any deferred validations of some nodes in the AST.
99 // This also allows post-processing of the AST for modification purposes.
100 $this->processDeferredIdentificationVariables();
101 if ($this->deferredPartialObjectExpressions) {
102 $this->processDeferredPartialObjectExpressions();
103 }
104 if ($this->deferredPathExpressions) {
105 $this->processDeferredPathExpressions();
106 }
107 if ($this->deferredResultVariables) {
108 $this->processDeferredResultVariables();
109 }
110 if ($this->deferredNewObjectExpressions) {
111 $this->processDeferredNewObjectExpressions($AST);
112 }
113 $this->processRootEntityAliasSelected();
114 // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
115 $this->fixIdentificationVariableOrder($AST);
116 return $AST;
117 }
118 public function match($token)
119 {
120 $lookaheadType = $this->lexer->lookahead->type ?? null;
121 // Short-circuit on first condition, usually types match
122 if ($lookaheadType === $token) {
123 $this->lexer->moveNext();
124 return;
125 }
126 // If parameter is not identifier (1-99) must be exact match
127 if ($token < TokenType::T_IDENTIFIER) {
128 $this->syntaxError($this->lexer->getLiteral($token));
129 }
130 // If parameter is keyword (200+) must be exact match
131 if ($token > TokenType::T_IDENTIFIER) {
132 $this->syntaxError($this->lexer->getLiteral($token));
133 }
134 // If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+)
135 if ($token === TokenType::T_IDENTIFIER && $lookaheadType < TokenType::T_IDENTIFIER) {
136 $this->syntaxError($this->lexer->getLiteral($token));
137 }
138 $this->lexer->moveNext();
139 }
140 public function free($deep = \false, $position = 0)
141 {
142 // WARNING! Use this method with care. It resets the scanner!
143 $this->lexer->resetPosition($position);
144 // Deep = true cleans peek and also any previously defined errors
145 if ($deep) {
146 $this->lexer->resetPeek();
147 }
148 $this->lexer->token = null;
149 $this->lexer->lookahead = null;
150 }
151 public function parse()
152 {
153 $AST = $this->getAST();
154 $customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
155 if ($customWalkers !== \false) {
156 $this->customTreeWalkers = $customWalkers;
157 }
158 $customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER);
159 if ($customOutputWalker !== \false) {
160 $this->customOutputWalker = $customOutputWalker;
161 }
162 // Run any custom tree walkers over the AST
163 if ($this->customTreeWalkers) {
164 $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents);
165 foreach ($this->customTreeWalkers as $walker) {
166 $treeWalkerChain->addTreeWalker($walker);
167 }
168 switch (\true) {
169 case $AST instanceof AST\UpdateStatement:
170 $treeWalkerChain->walkUpdateStatement($AST);
171 break;
172 case $AST instanceof AST\DeleteStatement:
173 $treeWalkerChain->walkDeleteStatement($AST);
174 break;
175 case $AST instanceof AST\SelectStatement:
176 default:
177 $treeWalkerChain->walkSelectStatement($AST);
178 }
179 $this->queryComponents = $treeWalkerChain->getQueryComponents();
180 }
181 $outputWalkerClass = $this->customOutputWalker ?: SqlOutputWalker::class;
182 $outputWalker = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
183 if ($outputWalker instanceof OutputWalker) {
184 $finalizer = $outputWalker->getFinalizer($AST);
185 $this->parserResult->setSqlFinalizer($finalizer);
186 } else {
187 Deprecation::trigger('doctrine/orm', 'https://github.com/doctrine/orm/pull/11188/', 'Your output walker class %s should implement %s in order to provide a %s. This also means the output walker should not use the query firstResult/maxResult values, which should be read from the query by the SqlFinalizer only.', $outputWalkerClass, OutputWalker::class, SqlFinalizer::class);
188 // @phpstan-ignore method.deprecated
189 $executor = $outputWalker->getExecutor($AST);
190 // @phpstan-ignore method.deprecated
191 $this->parserResult->setSqlExecutor($executor);
192 }
193 return $this->parserResult;
194 }
195 private function fixIdentificationVariableOrder(AST\Node $AST) : void
196 {
197 if (count($this->identVariableExpressions) <= 1) {
198 return;
199 }
200 assert($AST instanceof AST\SelectStatement);
201 foreach ($this->queryComponents as $dqlAlias => $qComp) {
202 if (!isset($this->identVariableExpressions[$dqlAlias])) {
203 continue;
204 }
205 $expr = $this->identVariableExpressions[$dqlAlias];
206 $key = array_search($expr, $AST->selectClause->selectExpressions, \true);
207 unset($AST->selectClause->selectExpressions[$key]);
208 $AST->selectClause->selectExpressions[] = $expr;
209 }
210 }
211 public function syntaxError($expected = '', $token = null)
212 {
213 if ($token === null) {
214 $token = $this->lexer->lookahead;
215 }
216 $tokenPos = $token->position ?? '-1';
217 $message = sprintf('line 0, col %d: Error: ', $tokenPos);
218 $message .= $expected !== '' ? sprintf('Expected %s, got ', $expected) : 'Unexpected ';
219 $message .= $this->lexer->lookahead === null ? 'end of string.' : sprintf("'%s'", $token->value);
220 throw QueryException::syntaxError($message, QueryException::dqlError($this->query->getDQL() ?? ''));
221 }
222 public function semanticalError($message = '', $token = null)
223 {
224 if ($token === null) {
225 $token = $this->lexer->lookahead ?? new Token('fake token', 42, 0);
226 }
227 // Minimum exposed chars ahead of token
228 $distance = 12;
229 // Find a position of a final word to display in error string
230 $dql = $this->query->getDQL();
231 $length = strlen($dql);
232 $pos = $token->position + $distance;
233 $pos = strpos($dql, ' ', $length > $pos ? $pos : $length);
234 $length = $pos !== \false ? $pos - $token->position : $distance;
235 $tokenPos = $token->position > 0 ? $token->position : '-1';
236 $tokenStr = substr($dql, $token->position, $length);
237 // Building informative message
238 $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message;
239 throw QueryException::semanticalError($message, QueryException::dqlError($this->query->getDQL()));
240 }
241 private function peekBeyondClosingParenthesis(bool $resetPeek = \true)
242 {
243 $token = $this->lexer->peek();
244 $numUnmatched = 1;
245 while ($numUnmatched > 0 && $token !== null) {
246 switch ($token->type) {
247 case TokenType::T_OPEN_PARENTHESIS:
248 ++$numUnmatched;
249 break;
250 case TokenType::T_CLOSE_PARENTHESIS:
251 --$numUnmatched;
252 break;
253 default:
254 }
255 $token = $this->lexer->peek();
256 }
257 if ($resetPeek) {
258 $this->lexer->resetPeek();
259 }
260 return $token;
261 }
262 private function isMathOperator($token) : bool
263 {
264 return $token !== null && in_array($token->type, [TokenType::T_PLUS, TokenType::T_MINUS, TokenType::T_DIVIDE, TokenType::T_MULTIPLY], \true);
265 }
266 private function isFunction() : bool
267 {
268 assert($this->lexer->lookahead !== null);
269 $lookaheadType = $this->lexer->lookahead->type;
270 $peek = $this->lexer->peek();
271 $this->lexer->resetPeek();
272 return $lookaheadType >= TokenType::T_IDENTIFIER && $peek !== null && $peek->type === TokenType::T_OPEN_PARENTHESIS;
273 }
274 private function isAggregateFunction(int $tokenType) : bool
275 {
276 return in_array($tokenType, [TokenType::T_AVG, TokenType::T_MIN, TokenType::T_MAX, TokenType::T_SUM, TokenType::T_COUNT], \true);
277 }
278 private function isNextAllAnySome() : bool
279 {
280 assert($this->lexer->lookahead !== null);
281 return in_array($this->lexer->lookahead->type, [TokenType::T_ALL, TokenType::T_ANY, TokenType::T_SOME], \true);
282 }
283 private function processDeferredIdentificationVariables() : void
284 {
285 foreach ($this->deferredIdentificationVariables as $deferredItem) {
286 $identVariable = $deferredItem['expression'];
287 // Check if IdentificationVariable exists in queryComponents
288 if (!isset($this->queryComponents[$identVariable])) {
289 $this->semanticalError(sprintf("'%s' is not defined.", $identVariable), $deferredItem['token']);
290 }
291 $qComp = $this->queryComponents[$identVariable];
292 // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
293 if (!isset($qComp['metadata'])) {
294 $this->semanticalError(sprintf("'%s' does not point to a Class.", $identVariable), $deferredItem['token']);
295 }
296 // Validate if identification variable nesting level is lower or equal than the current one
297 if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
298 $this->semanticalError(sprintf("'%s' is used outside the scope of its declaration.", $identVariable), $deferredItem['token']);
299 }
300 }
301 }
302 private function processDeferredNewObjectExpressions(AST\SelectStatement $AST) : void
303 {
304 foreach ($this->deferredNewObjectExpressions as $deferredItem) {
305 $expression = $deferredItem['expression'];
306 $token = $deferredItem['token'];
307 $className = $expression->className;
308 $args = $expression->args;
309 $fromClassName = $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName ?? null;
310 // If the namespace is not given then assumes the first FROM entity namespace
311 if (!str_contains($className, '\\') && !class_exists($className) && is_string($fromClassName) && str_contains($fromClassName, '\\')) {
312 $namespace = substr($fromClassName, 0, strrpos($fromClassName, '\\'));
313 $fqcn = $namespace . '\\' . $className;
314 if (class_exists($fqcn)) {
315 $expression->className = $fqcn;
316 $className = $fqcn;
317 }
318 }
319 if (!class_exists($className)) {
320 $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token);
321 }
322 $class = new ReflectionClass($className);
323 if (!$class->isInstantiable()) {
324 $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token);
325 }
326 if ($class->getConstructor() === null) {
327 $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token);
328 }
329 if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) {
330 $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token);
331 }
332 }
333 }
334 private function processDeferredPartialObjectExpressions() : void
335 {
336 foreach ($this->deferredPartialObjectExpressions as $deferredItem) {
337 $expr = $deferredItem['expression'];
338 $class = $this->getMetadataForDqlAlias($expr->identificationVariable);
339 foreach ($expr->partialFieldSet as $field) {
340 if (isset($class->fieldMappings[$field])) {
341 continue;
342 }
343 if (isset($class->associationMappings[$field]) && $class->associationMappings[$field]['isOwningSide'] && $class->associationMappings[$field]['type'] & ClassMetadata::TO_ONE) {
344 continue;
345 }
346 $this->semanticalError(sprintf("There is no mapped field named '%s' on class %s.", $field, $class->name), $deferredItem['token']);
347 }
348 if (array_intersect($class->identifier, $expr->partialFieldSet) !== $class->identifier) {
349 $this->semanticalError('The partial field selection of class ' . $class->name . ' must contain the identifier.', $deferredItem['token']);
350 }
351 }
352 }
353 private function processDeferredResultVariables() : void
354 {
355 foreach ($this->deferredResultVariables as $deferredItem) {
356 $resultVariable = $deferredItem['expression'];
357 // Check if ResultVariable exists in queryComponents
358 if (!isset($this->queryComponents[$resultVariable])) {
359 $this->semanticalError(sprintf("'%s' is not defined.", $resultVariable), $deferredItem['token']);
360 }
361 $qComp = $this->queryComponents[$resultVariable];
362 // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
363 if (!isset($qComp['resultVariable'])) {
364 $this->semanticalError(sprintf("'%s' does not point to a ResultVariable.", $resultVariable), $deferredItem['token']);
365 }
366 // Validate if identification variable nesting level is lower or equal than the current one
367 if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
368 $this->semanticalError(sprintf("'%s' is used outside the scope of its declaration.", $resultVariable), $deferredItem['token']);
369 }
370 }
371 }
372 private function processDeferredPathExpressions() : void
373 {
374 foreach ($this->deferredPathExpressions as $deferredItem) {
375 $pathExpression = $deferredItem['expression'];
376 $class = $this->getMetadataForDqlAlias($pathExpression->identificationVariable);
377 $field = $pathExpression->field;
378 if ($field === null) {
379 $field = $pathExpression->field = $class->identifier[0];
380 }
381 // Check if field or association exists
382 if (!isset($class->associationMappings[$field]) && !isset($class->fieldMappings[$field])) {
383 $this->semanticalError('Class ' . $class->name . ' has no field or association named ' . $field, $deferredItem['token']);
384 }
385 $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
386 if (isset($class->associationMappings[$field])) {
387 $assoc = $class->associationMappings[$field];
388 $fieldType = $assoc['type'] & ClassMetadata::TO_ONE ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
389 }
390 // Validate if PathExpression is one of the expected types
391 $expectedType = $pathExpression->expectedType;
392 if (!($expectedType & $fieldType)) {
393 // We need to recognize which was expected type(s)
394 $expectedStringTypes = [];
395 // Validate state field type
396 if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) {
397 $expectedStringTypes[] = 'StateFieldPathExpression';
398 }
399 // Validate single valued association (*-to-one)
400 if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
401 $expectedStringTypes[] = 'SingleValuedAssociationField';
402 }
403 // Validate single valued association (*-to-many)
404 if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
405 $expectedStringTypes[] = 'CollectionValuedAssociationField';
406 }
407 // Build the error message
408 $semanticalError = 'Invalid PathExpression. ';
409 $semanticalError .= count($expectedStringTypes) === 1 ? 'Must be a ' . $expectedStringTypes[0] . '.' : implode(' or ', $expectedStringTypes) . ' expected.';
410 $this->semanticalError($semanticalError, $deferredItem['token']);
411 }
412 // We need to force the type in PathExpression
413 $pathExpression->type = $fieldType;
414 }
415 }
416 private function processRootEntityAliasSelected() : void
417 {
418 if (!count($this->identVariableExpressions)) {
419 return;
420 }
421 foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
422 if (isset($this->queryComponents[$dqlAlias]) && !isset($this->queryComponents[$dqlAlias]['parent'])) {
423 return;
424 }
425 }
426 $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.');
427 }
428 public function QueryLanguage()
429 {
430 $statement = null;
431 $this->lexer->moveNext();
432 switch ($this->lexer->lookahead->type ?? null) {
433 case TokenType::T_SELECT:
434 $statement = $this->SelectStatement();
435 break;
436 case TokenType::T_UPDATE:
437 $statement = $this->UpdateStatement();
438 break;
439 case TokenType::T_DELETE:
440 $statement = $this->DeleteStatement();
441 break;
442 default:
443 $this->syntaxError('SELECT, UPDATE or DELETE');
444 break;
445 }
446 // Check for end of string
447 if ($this->lexer->lookahead !== null) {
448 $this->syntaxError('end of string');
449 }
450 return $statement;
451 }
452 public function SelectStatement()
453 {
454 $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
455 $selectStatement->whereClause = $this->lexer->isNextToken(TokenType::T_WHERE) ? $this->WhereClause() : null;
456 $selectStatement->groupByClause = $this->lexer->isNextToken(TokenType::T_GROUP) ? $this->GroupByClause() : null;
457 $selectStatement->havingClause = $this->lexer->isNextToken(TokenType::T_HAVING) ? $this->HavingClause() : null;
458 $selectStatement->orderByClause = $this->lexer->isNextToken(TokenType::T_ORDER) ? $this->OrderByClause() : null;
459 return $selectStatement;
460 }
461 public function UpdateStatement()
462 {
463 $updateStatement = new AST\UpdateStatement($this->UpdateClause());
464 $updateStatement->whereClause = $this->lexer->isNextToken(TokenType::T_WHERE) ? $this->WhereClause() : null;
465 return $updateStatement;
466 }
467 public function DeleteStatement()
468 {
469 $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
470 $deleteStatement->whereClause = $this->lexer->isNextToken(TokenType::T_WHERE) ? $this->WhereClause() : null;
471 return $deleteStatement;
472 }
473 public function IdentificationVariable()
474 {
475 $this->match(TokenType::T_IDENTIFIER);
476 assert($this->lexer->token !== null);
477 $identVariable = $this->lexer->token->value;
478 $this->deferredIdentificationVariables[] = ['expression' => $identVariable, 'nestingLevel' => $this->nestingLevel, 'token' => $this->lexer->token];
479 return $identVariable;
480 }
481 public function AliasIdentificationVariable()
482 {
483 $this->match(TokenType::T_IDENTIFIER);
484 assert($this->lexer->token !== null);
485 $aliasIdentVariable = $this->lexer->token->value;
486 $exists = isset($this->queryComponents[$aliasIdentVariable]);
487 if ($exists) {
488 $this->semanticalError(sprintf("'%s' is already defined.", $aliasIdentVariable), $this->lexer->token);
489 }
490 return $aliasIdentVariable;
491 }
492 public function AbstractSchemaName()
493 {
494 if ($this->lexer->isNextToken(TokenType::T_FULLY_QUALIFIED_NAME)) {
495 $this->match(TokenType::T_FULLY_QUALIFIED_NAME);
496 assert($this->lexer->token !== null);
497 return $this->lexer->token->value;
498 }
499 if ($this->lexer->isNextToken(TokenType::T_IDENTIFIER)) {
500 $this->match(TokenType::T_IDENTIFIER);
501 assert($this->lexer->token !== null);
502 return $this->lexer->token->value;
503 }
504 // @phpstan-ignore classConstant.deprecated
505 $this->match(TokenType::T_ALIASED_NAME);
506 assert($this->lexer->token !== null);
507 Deprecation::trigger('doctrine/orm', 'https://github.com/doctrine/orm/issues/8818', 'Short namespace aliases such as "%s" are deprecated and will be removed in Doctrine ORM 3.0.', $this->lexer->token->value);
508 [$namespaceAlias, $simpleClassName] = explode(':', $this->lexer->token->value);
509 return $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
510 }
511 private function validateAbstractSchemaName(string $schemaName) : void
512 {
513 assert($this->lexer->token !== null);
514 if (!(class_exists($schemaName, \true) || interface_exists($schemaName, \true))) {
515 $this->semanticalError(sprintf("Class '%s' is not defined.", $schemaName), $this->lexer->token);
516 }
517 }
518 public function AliasResultVariable()
519 {
520 $this->match(TokenType::T_IDENTIFIER);
521 assert($this->lexer->token !== null);
522 $resultVariable = $this->lexer->token->value;
523 $exists = isset($this->queryComponents[$resultVariable]);
524 if ($exists) {
525 $this->semanticalError(sprintf("'%s' is already defined.", $resultVariable), $this->lexer->token);
526 }
527 return $resultVariable;
528 }
529 public function ResultVariable()
530 {
531 $this->match(TokenType::T_IDENTIFIER);
532 assert($this->lexer->token !== null);
533 $resultVariable = $this->lexer->token->value;
534 // Defer ResultVariable validation
535 $this->deferredResultVariables[] = ['expression' => $resultVariable, 'nestingLevel' => $this->nestingLevel, 'token' => $this->lexer->token];
536 return $resultVariable;
537 }
538 public function JoinAssociationPathExpression()
539 {
540 $identVariable = $this->IdentificationVariable();
541 if (!isset($this->queryComponents[$identVariable])) {
542 $this->semanticalError('Identification Variable ' . $identVariable . ' used in join path expression but was not defined before.');
543 }
544 $this->match(TokenType::T_DOT);
545 $this->match(TokenType::T_IDENTIFIER);
546 assert($this->lexer->token !== null);
547 $field = $this->lexer->token->value;
548 // Validate association field
549 $class = $this->getMetadataForDqlAlias($identVariable);
550 if (!$class->hasAssociation($field)) {
551 $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
552 }
553 return new AST\JoinAssociationPathExpression($identVariable, $field);
554 }
555 public function PathExpression($expectedTypes)
556 {
557 $identVariable = $this->IdentificationVariable();
558 $field = null;
559 assert($this->lexer->token !== null);
560 if ($this->lexer->isNextToken(TokenType::T_DOT)) {
561 $this->match(TokenType::T_DOT);
562 $this->match(TokenType::T_IDENTIFIER);
563 $field = $this->lexer->token->value;
564 while ($this->lexer->isNextToken(TokenType::T_DOT)) {
565 $this->match(TokenType::T_DOT);
566 $this->match(TokenType::T_IDENTIFIER);
567 $field .= '.' . $this->lexer->token->value;
568 }
569 }
570 // Creating AST node
571 $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
572 // Defer PathExpression validation if requested to be deferred
573 $this->deferredPathExpressions[] = ['expression' => $pathExpr, 'nestingLevel' => $this->nestingLevel, 'token' => $this->lexer->token];
574 return $pathExpr;
575 }
576 public function AssociationPathExpression()
577 {
578 return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION | AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
579 }
580 public function SingleValuedPathExpression()
581 {
582 return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD | AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
583 }
584 public function StateFieldPathExpression()
585 {
586 return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
587 }
588 public function SingleValuedAssociationPathExpression()
589 {
590 return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
591 }
592 public function CollectionValuedPathExpression()
593 {
594 return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
595 }
596 public function SelectClause()
597 {
598 $isDistinct = \false;
599 $this->match(TokenType::T_SELECT);
600 // Check for DISTINCT
601 if ($this->lexer->isNextToken(TokenType::T_DISTINCT)) {
602 $this->match(TokenType::T_DISTINCT);
603 $isDistinct = \true;
604 }
605 // Process SelectExpressions (1..N)
606 $selectExpressions = [];
607 $selectExpressions[] = $this->SelectExpression();
608 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
609 $this->match(TokenType::T_COMMA);
610 $selectExpressions[] = $this->SelectExpression();
611 }
612 return new AST\SelectClause($selectExpressions, $isDistinct);
613 }
614 public function SimpleSelectClause()
615 {
616 $isDistinct = \false;
617 $this->match(TokenType::T_SELECT);
618 if ($this->lexer->isNextToken(TokenType::T_DISTINCT)) {
619 $this->match(TokenType::T_DISTINCT);
620 $isDistinct = \true;
621 }
622 return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct);
623 }
624 public function UpdateClause()
625 {
626 $this->match(TokenType::T_UPDATE);
627 assert($this->lexer->lookahead !== null);
628 $token = $this->lexer->lookahead;
629 $abstractSchemaName = $this->AbstractSchemaName();
630 $this->validateAbstractSchemaName($abstractSchemaName);
631 if ($this->lexer->isNextToken(TokenType::T_AS)) {
632 $this->match(TokenType::T_AS);
633 }
634 $aliasIdentificationVariable = $this->AliasIdentificationVariable();
635 $class = $this->em->getClassMetadata($abstractSchemaName);
636 // Building queryComponent
637 $queryComponent = ['metadata' => $class, 'parent' => null, 'relation' => null, 'map' => null, 'nestingLevel' => $this->nestingLevel, 'token' => $token];
638 $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
639 $this->match(TokenType::T_SET);
640 $updateItems = [];
641 $updateItems[] = $this->UpdateItem();
642 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
643 $this->match(TokenType::T_COMMA);
644 $updateItems[] = $this->UpdateItem();
645 }
646 $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems);
647 $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable;
648 return $updateClause;
649 }
650 public function DeleteClause()
651 {
652 $this->match(TokenType::T_DELETE);
653 if ($this->lexer->isNextToken(TokenType::T_FROM)) {
654 $this->match(TokenType::T_FROM);
655 }
656 assert($this->lexer->lookahead !== null);
657 $token = $this->lexer->lookahead;
658 $abstractSchemaName = $this->AbstractSchemaName();
659 $this->validateAbstractSchemaName($abstractSchemaName);
660 $deleteClause = new AST\DeleteClause($abstractSchemaName);
661 if ($this->lexer->isNextToken(TokenType::T_AS)) {
662 $this->match(TokenType::T_AS);
663 }
664 $aliasIdentificationVariable = $this->lexer->isNextToken(TokenType::T_IDENTIFIER) ? $this->AliasIdentificationVariable() : 'alias_should_have_been_set';
665 $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
666 $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
667 // Building queryComponent
668 $queryComponent = ['metadata' => $class, 'parent' => null, 'relation' => null, 'map' => null, 'nestingLevel' => $this->nestingLevel, 'token' => $token];
669 $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
670 return $deleteClause;
671 }
672 public function FromClause()
673 {
674 $this->match(TokenType::T_FROM);
675 $identificationVariableDeclarations = [];
676 $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
677 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
678 $this->match(TokenType::T_COMMA);
679 $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
680 }
681 return new AST\FromClause($identificationVariableDeclarations);
682 }
683 public function SubselectFromClause()
684 {
685 $this->match(TokenType::T_FROM);
686 $identificationVariables = [];
687 $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
688 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
689 $this->match(TokenType::T_COMMA);
690 $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
691 }
692 return new AST\SubselectFromClause($identificationVariables);
693 }
694 public function WhereClause()
695 {
696 $this->match(TokenType::T_WHERE);
697 return new AST\WhereClause($this->ConditionalExpression());
698 }
699 public function HavingClause()
700 {
701 $this->match(TokenType::T_HAVING);
702 return new AST\HavingClause($this->ConditionalExpression());
703 }
704 public function GroupByClause()
705 {
706 $this->match(TokenType::T_GROUP);
707 $this->match(TokenType::T_BY);
708 $groupByItems = [$this->GroupByItem()];
709 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
710 $this->match(TokenType::T_COMMA);
711 $groupByItems[] = $this->GroupByItem();
712 }
713 return new AST\GroupByClause($groupByItems);
714 }
715 public function OrderByClause()
716 {
717 $this->match(TokenType::T_ORDER);
718 $this->match(TokenType::T_BY);
719 $orderByItems = [];
720 $orderByItems[] = $this->OrderByItem();
721 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
722 $this->match(TokenType::T_COMMA);
723 $orderByItems[] = $this->OrderByItem();
724 }
725 return new AST\OrderByClause($orderByItems);
726 }
727 public function Subselect()
728 {
729 // Increase query nesting level
730 $this->nestingLevel++;
731 $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
732 $subselect->whereClause = $this->lexer->isNextToken(TokenType::T_WHERE) ? $this->WhereClause() : null;
733 $subselect->groupByClause = $this->lexer->isNextToken(TokenType::T_GROUP) ? $this->GroupByClause() : null;
734 $subselect->havingClause = $this->lexer->isNextToken(TokenType::T_HAVING) ? $this->HavingClause() : null;
735 $subselect->orderByClause = $this->lexer->isNextToken(TokenType::T_ORDER) ? $this->OrderByClause() : null;
736 // Decrease query nesting level
737 $this->nestingLevel--;
738 return $subselect;
739 }
740 public function UpdateItem()
741 {
742 $pathExpr = $this->SingleValuedPathExpression();
743 $this->match(TokenType::T_EQUALS);
744 return new AST\UpdateItem($pathExpr, $this->NewValue());
745 }
746 public function GroupByItem()
747 {
748 // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
749 $glimpse = $this->lexer->glimpse();
750 if ($glimpse !== null && $glimpse->type === TokenType::T_DOT) {
751 return $this->SingleValuedPathExpression();
752 }
753 assert($this->lexer->lookahead !== null);
754 // Still need to decide between IdentificationVariable or ResultVariable
755 $lookaheadValue = $this->lexer->lookahead->value;
756 if (!isset($this->queryComponents[$lookaheadValue])) {
757 $this->semanticalError('Cannot group by undefined identification or result variable.');
758 }
759 return isset($this->queryComponents[$lookaheadValue]['metadata']) ? $this->IdentificationVariable() : $this->ResultVariable();
760 }
761 public function OrderByItem()
762 {
763 $this->lexer->peek();
764 // lookahead => '.'
765 $this->lexer->peek();
766 // lookahead => token after '.'
767 $peek = $this->lexer->peek();
768 // lookahead => token after the token after the '.'
769 $this->lexer->resetPeek();
770 $glimpse = $this->lexer->glimpse();
771 assert($this->lexer->lookahead !== null);
772 switch (\true) {
773 case $this->isMathOperator($peek) || $this->isMathOperator($glimpse):
774 $expr = $this->SimpleArithmeticExpression();
775 break;
776 case $glimpse !== null && $glimpse->type === TokenType::T_DOT:
777 $expr = $this->SingleValuedPathExpression();
778 break;
779 case $this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis()):
780 $expr = $this->ScalarExpression();
781 break;
782 case $this->lexer->lookahead->type === TokenType::T_CASE:
783 $expr = $this->CaseExpression();
784 break;
785 case $this->isFunction():
786 $expr = $this->FunctionDeclaration();
787 break;
788 default:
789 $expr = $this->ResultVariable();
790 break;
791 }
792 $type = 'ASC';
793 $item = new AST\OrderByItem($expr);
794 switch (\true) {
795 case $this->lexer->isNextToken(TokenType::T_DESC):
796 $this->match(TokenType::T_DESC);
797 $type = 'DESC';
798 break;
799 case $this->lexer->isNextToken(TokenType::T_ASC):
800 $this->match(TokenType::T_ASC);
801 break;
802 default:
803 }
804 $item->type = $type;
805 return $item;
806 }
807 public function NewValue()
808 {
809 if ($this->lexer->isNextToken(TokenType::T_NULL)) {
810 $this->match(TokenType::T_NULL);
811 return null;
812 }
813 if ($this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER)) {
814 $this->match(TokenType::T_INPUT_PARAMETER);
815 assert($this->lexer->token !== null);
816 return new AST\InputParameter($this->lexer->token->value);
817 }
818 return $this->ArithmeticExpression();
819 }
820 public function IdentificationVariableDeclaration()
821 {
822 $joins = [];
823 $rangeVariableDeclaration = $this->RangeVariableDeclaration();
824 $indexBy = $this->lexer->isNextToken(TokenType::T_INDEX) ? $this->IndexBy() : null;
825 $rangeVariableDeclaration->isRoot = \true;
826 while ($this->lexer->isNextToken(TokenType::T_LEFT) || $this->lexer->isNextToken(TokenType::T_INNER) || $this->lexer->isNextToken(TokenType::T_JOIN)) {
827 $joins[] = $this->Join();
828 }
829 return new AST\IdentificationVariableDeclaration($rangeVariableDeclaration, $indexBy, $joins);
830 }
831 public function SubselectIdentificationVariableDeclaration()
832 {
833 return $this->IdentificationVariableDeclaration();
834 }
835 public function Join()
836 {
837 // Check Join type
838 $joinType = AST\Join::JOIN_TYPE_INNER;
839 switch (\true) {
840 case $this->lexer->isNextToken(TokenType::T_LEFT):
841 $this->match(TokenType::T_LEFT);
842 $joinType = AST\Join::JOIN_TYPE_LEFT;
843 // Possible LEFT OUTER join
844 if ($this->lexer->isNextToken(TokenType::T_OUTER)) {
845 $this->match(TokenType::T_OUTER);
846 $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
847 }
848 break;
849 case $this->lexer->isNextToken(TokenType::T_INNER):
850 $this->match(TokenType::T_INNER);
851 break;
852 default:
853 }
854 $this->match(TokenType::T_JOIN);
855 $next = $this->lexer->glimpse();
856 assert($next !== null);
857 $joinDeclaration = $next->type === TokenType::T_DOT ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
858 $adhocConditions = $this->lexer->isNextToken(TokenType::T_WITH);
859 $join = new AST\Join($joinType, $joinDeclaration);
860 // Describe non-root join declaration
861 if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
862 $joinDeclaration->isRoot = \false;
863 }
864 // Check for ad-hoc Join conditions
865 if ($adhocConditions) {
866 $this->match(TokenType::T_WITH);
867 $join->conditionalExpression = $this->ConditionalExpression();
868 }
869 return $join;
870 }
871 public function RangeVariableDeclaration()
872 {
873 if ($this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS) && $this->lexer->glimpse()->type === TokenType::T_SELECT) {
874 $this->semanticalError('Subquery is not supported here', $this->lexer->token);
875 }
876 $abstractSchemaName = $this->AbstractSchemaName();
877 $this->validateAbstractSchemaName($abstractSchemaName);
878 if ($this->lexer->isNextToken(TokenType::T_AS)) {
879 $this->match(TokenType::T_AS);
880 }
881 assert($this->lexer->lookahead !== null);
882 $token = $this->lexer->lookahead;
883 $aliasIdentificationVariable = $this->AliasIdentificationVariable();
884 $classMetadata = $this->em->getClassMetadata($abstractSchemaName);
885 // Building queryComponent
886 $queryComponent = ['metadata' => $classMetadata, 'parent' => null, 'relation' => null, 'map' => null, 'nestingLevel' => $this->nestingLevel, 'token' => $token];
887 $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
888 return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable);
889 }
890 public function JoinAssociationDeclaration()
891 {
892 $joinAssociationPathExpression = $this->JoinAssociationPathExpression();
893 if ($this->lexer->isNextToken(TokenType::T_AS)) {
894 $this->match(TokenType::T_AS);
895 }
896 assert($this->lexer->lookahead !== null);
897 $aliasIdentificationVariable = $this->AliasIdentificationVariable();
898 $indexBy = $this->lexer->isNextToken(TokenType::T_INDEX) ? $this->IndexBy() : null;
899 $identificationVariable = $joinAssociationPathExpression->identificationVariable;
900 $field = $joinAssociationPathExpression->associationField;
901 $class = $this->getMetadataForDqlAlias($identificationVariable);
902 $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
903 // Building queryComponent
904 $joinQueryComponent = ['metadata' => $targetClass, 'parent' => $joinAssociationPathExpression->identificationVariable, 'relation' => $class->getAssociationMapping($field), 'map' => null, 'nestingLevel' => $this->nestingLevel, 'token' => $this->lexer->lookahead];
905 $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
906 return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy);
907 }
908 public function PartialObjectExpression()
909 {
910 $this->match(TokenType::T_PARTIAL);
911 $partialFieldSet = [];
912 $identificationVariable = $this->IdentificationVariable();
913 $this->match(TokenType::T_DOT);
914 $this->match(TokenType::T_OPEN_CURLY_BRACE);
915 $this->match(TokenType::T_IDENTIFIER);
916 assert($this->lexer->token !== null);
917 $field = $this->lexer->token->value;
918 // First field in partial expression might be embeddable property
919 while ($this->lexer->isNextToken(TokenType::T_DOT)) {
920 $this->match(TokenType::T_DOT);
921 $this->match(TokenType::T_IDENTIFIER);
922 $field .= '.' . $this->lexer->token->value;
923 }
924 $partialFieldSet[] = $field;
925 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
926 $this->match(TokenType::T_COMMA);
927 $this->match(TokenType::T_IDENTIFIER);
928 $field = $this->lexer->token->value;
929 while ($this->lexer->isNextToken(TokenType::T_DOT)) {
930 $this->match(TokenType::T_DOT);
931 $this->match(TokenType::T_IDENTIFIER);
932 $field .= '.' . $this->lexer->token->value;
933 }
934 $partialFieldSet[] = $field;
935 }
936 $this->match(TokenType::T_CLOSE_CURLY_BRACE);
937 $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
938 // Defer PartialObjectExpression validation
939 $this->deferredPartialObjectExpressions[] = ['expression' => $partialObjectExpression, 'nestingLevel' => $this->nestingLevel, 'token' => $this->lexer->token];
940 return $partialObjectExpression;
941 }
942 public function NewObjectExpression()
943 {
944 $this->match(TokenType::T_NEW);
945 $className = $this->AbstractSchemaName();
946 // note that this is not yet validated
947 $token = $this->lexer->token;
948 $this->match(TokenType::T_OPEN_PARENTHESIS);
949 $args[] = $this->NewObjectArg();
950 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
951 $this->match(TokenType::T_COMMA);
952 $args[] = $this->NewObjectArg();
953 }
954 $this->match(TokenType::T_CLOSE_PARENTHESIS);
955 $expression = new AST\NewObjectExpression($className, $args);
956 // Defer NewObjectExpression validation
957 $this->deferredNewObjectExpressions[] = ['token' => $token, 'expression' => $expression, 'nestingLevel' => $this->nestingLevel];
958 return $expression;
959 }
960 public function NewObjectArg()
961 {
962 assert($this->lexer->lookahead !== null);
963 $token = $this->lexer->lookahead;
964 $peek = $this->lexer->glimpse();
965 assert($peek !== null);
966 if ($token->type === TokenType::T_OPEN_PARENTHESIS && $peek->type === TokenType::T_SELECT) {
967 $this->match(TokenType::T_OPEN_PARENTHESIS);
968 $expression = $this->Subselect();
969 $this->match(TokenType::T_CLOSE_PARENTHESIS);
970 return $expression;
971 }
972 return $this->ScalarExpression();
973 }
974 public function IndexBy()
975 {
976 $this->match(TokenType::T_INDEX);
977 $this->match(TokenType::T_BY);
978 $pathExpr = $this->SingleValuedPathExpression();
979 // Add the INDEX BY info to the query component
980 $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
981 return new AST\IndexBy($pathExpr);
982 }
983 public function ScalarExpression()
984 {
985 assert($this->lexer->token !== null);
986 assert($this->lexer->lookahead !== null);
987 $lookahead = $this->lexer->lookahead->type;
988 $peek = $this->lexer->glimpse();
989 switch (\true) {
990 case $lookahead === TokenType::T_INTEGER:
991 case $lookahead === TokenType::T_FLOAT:
992 // SimpleArithmeticExpression : (- u.value ) or ( + u.value ) or ( - 1 ) or ( + 1 )
993 case $lookahead === TokenType::T_MINUS:
994 case $lookahead === TokenType::T_PLUS:
995 return $this->SimpleArithmeticExpression();
996 case $lookahead === TokenType::T_STRING:
997 return $this->StringPrimary();
998 case $lookahead === TokenType::T_TRUE:
999 case $lookahead === TokenType::T_FALSE:
1000 $this->match($lookahead);
1001 return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token->value);
1002 case $lookahead === TokenType::T_INPUT_PARAMETER:
1003 switch (\true) {
1004 case $this->isMathOperator($peek):
1005 // :param + u.value
1006 return $this->SimpleArithmeticExpression();
1007 default:
1008 return $this->InputParameter();
1009 }
1010 case $lookahead === TokenType::T_CASE:
1011 case $lookahead === TokenType::T_COALESCE:
1012 case $lookahead === TokenType::T_NULLIF:
1013 // Since NULLIF and COALESCE can be identified as a function,
1014 // we need to check these before checking for FunctionDeclaration
1015 return $this->CaseExpression();
1016 case $lookahead === TokenType::T_OPEN_PARENTHESIS:
1017 return $this->SimpleArithmeticExpression();
1018 // this check must be done before checking for a filed path expression
1019 case $this->isFunction():
1020 $this->lexer->peek();
1021 // "("
1022 switch (\true) {
1023 case $this->isMathOperator($this->peekBeyondClosingParenthesis()):
1024 // SUM(u.id) + COUNT(u.id)
1025 return $this->SimpleArithmeticExpression();
1026 default:
1027 // IDENTITY(u)
1028 return $this->FunctionDeclaration();
1029 }
1030 break;
1031 // it is no function, so it must be a field path
1032 case $lookahead === TokenType::T_IDENTIFIER:
1033 $this->lexer->peek();
1034 // lookahead => '.'
1035 $this->lexer->peek();
1036 // lookahead => token after '.'
1037 $peek = $this->lexer->peek();
1038 // lookahead => token after the token after the '.'
1039 $this->lexer->resetPeek();
1040 if ($this->isMathOperator($peek)) {
1041 return $this->SimpleArithmeticExpression();
1042 }
1043 return $this->StateFieldPathExpression();
1044 default:
1045 $this->syntaxError();
1046 }
1047 }
1048 public function CaseExpression()
1049 {
1050 assert($this->lexer->lookahead !== null);
1051 $lookahead = $this->lexer->lookahead->type;
1052 switch ($lookahead) {
1053 case TokenType::T_NULLIF:
1054 return $this->NullIfExpression();
1055 case TokenType::T_COALESCE:
1056 return $this->CoalesceExpression();
1057 case TokenType::T_CASE:
1058 $this->lexer->resetPeek();
1059 $peek = $this->lexer->peek();
1060 assert($peek !== null);
1061 if ($peek->type === TokenType::T_WHEN) {
1062 return $this->GeneralCaseExpression();
1063 }
1064 return $this->SimpleCaseExpression();
1065 default:
1066 // Do nothing
1067 break;
1068 }
1069 $this->syntaxError();
1070 }
1071 public function CoalesceExpression()
1072 {
1073 $this->match(TokenType::T_COALESCE);
1074 $this->match(TokenType::T_OPEN_PARENTHESIS);
1075 // Process ScalarExpressions (1..N)
1076 $scalarExpressions = [];
1077 $scalarExpressions[] = $this->ScalarExpression();
1078 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
1079 $this->match(TokenType::T_COMMA);
1080 $scalarExpressions[] = $this->ScalarExpression();
1081 }
1082 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1083 return new AST\CoalesceExpression($scalarExpressions);
1084 }
1085 public function NullIfExpression()
1086 {
1087 $this->match(TokenType::T_NULLIF);
1088 $this->match(TokenType::T_OPEN_PARENTHESIS);
1089 $firstExpression = $this->ScalarExpression();
1090 $this->match(TokenType::T_COMMA);
1091 $secondExpression = $this->ScalarExpression();
1092 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1093 return new AST\NullIfExpression($firstExpression, $secondExpression);
1094 }
1095 public function GeneralCaseExpression()
1096 {
1097 $this->match(TokenType::T_CASE);
1098 // Process WhenClause (1..N)
1099 $whenClauses = [];
1100 do {
1101 $whenClauses[] = $this->WhenClause();
1102 } while ($this->lexer->isNextToken(TokenType::T_WHEN));
1103 $this->match(TokenType::T_ELSE);
1104 $scalarExpression = $this->ScalarExpression();
1105 $this->match(TokenType::T_END);
1106 return new AST\GeneralCaseExpression($whenClauses, $scalarExpression);
1107 }
1108 public function SimpleCaseExpression()
1109 {
1110 $this->match(TokenType::T_CASE);
1111 $caseOperand = $this->StateFieldPathExpression();
1112 // Process SimpleWhenClause (1..N)
1113 $simpleWhenClauses = [];
1114 do {
1115 $simpleWhenClauses[] = $this->SimpleWhenClause();
1116 } while ($this->lexer->isNextToken(TokenType::T_WHEN));
1117 $this->match(TokenType::T_ELSE);
1118 $scalarExpression = $this->ScalarExpression();
1119 $this->match(TokenType::T_END);
1120 return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression);
1121 }
1122 public function WhenClause()
1123 {
1124 $this->match(TokenType::T_WHEN);
1125 $conditionalExpression = $this->ConditionalExpression();
1126 $this->match(TokenType::T_THEN);
1127 return new AST\WhenClause($conditionalExpression, $this->ScalarExpression());
1128 }
1129 public function SimpleWhenClause()
1130 {
1131 $this->match(TokenType::T_WHEN);
1132 $conditionalExpression = $this->ScalarExpression();
1133 $this->match(TokenType::T_THEN);
1134 return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression());
1135 }
1136 public function SelectExpression()
1137 {
1138 assert($this->lexer->lookahead !== null);
1139 $expression = null;
1140 $identVariable = null;
1141 $peek = $this->lexer->glimpse();
1142 $lookaheadType = $this->lexer->lookahead->type;
1143 assert($peek !== null);
1144 switch (\true) {
1145 // ScalarExpression (u.name)
1146 case $lookaheadType === TokenType::T_IDENTIFIER && $peek->type === TokenType::T_DOT:
1147 $expression = $this->ScalarExpression();
1148 break;
1149 // IdentificationVariable (u)
1150 case $lookaheadType === TokenType::T_IDENTIFIER && $peek->type !== TokenType::T_OPEN_PARENTHESIS:
1151 $expression = $identVariable = $this->IdentificationVariable();
1152 break;
1153 // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
1154 case $lookaheadType === TokenType::T_CASE:
1155 case $lookaheadType === TokenType::T_COALESCE:
1156 case $lookaheadType === TokenType::T_NULLIF:
1157 $expression = $this->CaseExpression();
1158 break;
1159 // DQL Function (SUM(u.value) or SUM(u.value) + 1)
1160 case $this->isFunction():
1161 $this->lexer->peek();
1162 // "("
1163 switch (\true) {
1164 case $this->isMathOperator($this->peekBeyondClosingParenthesis()):
1165 // SUM(u.id) + COUNT(u.id)
1166 $expression = $this->ScalarExpression();
1167 break;
1168 default:
1169 // IDENTITY(u)
1170 $expression = $this->FunctionDeclaration();
1171 break;
1172 }
1173 break;
1174 // PartialObjectExpression (PARTIAL u.{id, name})
1175 case $lookaheadType === TokenType::T_PARTIAL:
1176 $expression = $this->PartialObjectExpression();
1177 $identVariable = $expression->identificationVariable;
1178 break;
1179 // Subselect
1180 case $lookaheadType === TokenType::T_OPEN_PARENTHESIS && $peek->type === TokenType::T_SELECT:
1181 $this->match(TokenType::T_OPEN_PARENTHESIS);
1182 $expression = $this->Subselect();
1183 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1184 break;
1185 // Shortcut: ScalarExpression => SimpleArithmeticExpression
1186 case $lookaheadType === TokenType::T_OPEN_PARENTHESIS:
1187 case $lookaheadType === TokenType::T_INTEGER:
1188 case $lookaheadType === TokenType::T_STRING:
1189 case $lookaheadType === TokenType::T_FLOAT:
1190 // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
1191 case $lookaheadType === TokenType::T_MINUS:
1192 case $lookaheadType === TokenType::T_PLUS:
1193 $expression = $this->SimpleArithmeticExpression();
1194 break;
1195 // NewObjectExpression (New ClassName(id, name))
1196 case $lookaheadType === TokenType::T_NEW:
1197 $expression = $this->NewObjectExpression();
1198 break;
1199 default:
1200 $this->syntaxError('IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression', $this->lexer->lookahead);
1201 }
1202 // [["AS"] ["HIDDEN"] AliasResultVariable]
1203 $mustHaveAliasResultVariable = \false;
1204 if ($this->lexer->isNextToken(TokenType::T_AS)) {
1205 $this->match(TokenType::T_AS);
1206 $mustHaveAliasResultVariable = \true;
1207 }
1208 $hiddenAliasResultVariable = \false;
1209 if ($this->lexer->isNextToken(TokenType::T_HIDDEN)) {
1210 $this->match(TokenType::T_HIDDEN);
1211 $hiddenAliasResultVariable = \true;
1212 }
1213 $aliasResultVariable = null;
1214 if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(TokenType::T_IDENTIFIER)) {
1215 assert($expression instanceof AST\Node || is_string($expression));
1216 $token = $this->lexer->lookahead;
1217 $aliasResultVariable = $this->AliasResultVariable();
1218 // Include AliasResultVariable in query components.
1219 $this->queryComponents[$aliasResultVariable] = ['resultVariable' => $expression, 'nestingLevel' => $this->nestingLevel, 'token' => $token];
1220 }
1221 // AST
1222 $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
1223 if ($identVariable) {
1224 $this->identVariableExpressions[$identVariable] = $expr;
1225 }
1226 return $expr;
1227 }
1228 public function SimpleSelectExpression()
1229 {
1230 assert($this->lexer->lookahead !== null);
1231 $peek = $this->lexer->glimpse();
1232 assert($peek !== null);
1233 switch ($this->lexer->lookahead->type) {
1234 case TokenType::T_IDENTIFIER:
1235 switch (\true) {
1236 case $peek->type === TokenType::T_DOT:
1237 $expression = $this->StateFieldPathExpression();
1238 return new AST\SimpleSelectExpression($expression);
1239 case $peek->type !== TokenType::T_OPEN_PARENTHESIS:
1240 $expression = $this->IdentificationVariable();
1241 return new AST\SimpleSelectExpression($expression);
1242 case $this->isFunction():
1243 // SUM(u.id) + COUNT(u.id)
1244 if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) {
1245 return new AST\SimpleSelectExpression($this->ScalarExpression());
1246 }
1247 // COUNT(u.id)
1248 if ($this->isAggregateFunction($this->lexer->lookahead->type)) {
1249 return new AST\SimpleSelectExpression($this->AggregateExpression());
1250 }
1251 // IDENTITY(u)
1252 return new AST\SimpleSelectExpression($this->FunctionDeclaration());
1253 default:
1254 }
1255 break;
1256 case TokenType::T_OPEN_PARENTHESIS:
1257 if ($peek->type !== TokenType::T_SELECT) {
1258 // Shortcut: ScalarExpression => SimpleArithmeticExpression
1259 $expression = $this->SimpleArithmeticExpression();
1260 return new AST\SimpleSelectExpression($expression);
1261 }
1262 // Subselect
1263 $this->match(TokenType::T_OPEN_PARENTHESIS);
1264 $expression = $this->Subselect();
1265 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1266 return new AST\SimpleSelectExpression($expression);
1267 default:
1268 }
1269 $this->lexer->peek();
1270 $expression = $this->ScalarExpression();
1271 $expr = new AST\SimpleSelectExpression($expression);
1272 if ($this->lexer->isNextToken(TokenType::T_AS)) {
1273 $this->match(TokenType::T_AS);
1274 }
1275 if ($this->lexer->isNextToken(TokenType::T_IDENTIFIER)) {
1276 $token = $this->lexer->lookahead;
1277 $resultVariable = $this->AliasResultVariable();
1278 $expr->fieldIdentificationVariable = $resultVariable;
1279 // Include AliasResultVariable in query components.
1280 $this->queryComponents[$resultVariable] = ['resultvariable' => $expr, 'nestingLevel' => $this->nestingLevel, 'token' => $token];
1281 }
1282 return $expr;
1283 }
1284 public function ConditionalExpression()
1285 {
1286 $conditionalTerms = [];
1287 $conditionalTerms[] = $this->ConditionalTerm();
1288 while ($this->lexer->isNextToken(TokenType::T_OR)) {
1289 $this->match(TokenType::T_OR);
1290 $conditionalTerms[] = $this->ConditionalTerm();
1291 }
1292 // Phase 1 AST optimization: Prevent AST\ConditionalExpression
1293 // if only one AST\ConditionalTerm is defined
1294 if (count($conditionalTerms) === 1) {
1295 return $conditionalTerms[0];
1296 }
1297 return new AST\ConditionalExpression($conditionalTerms);
1298 }
1299 public function ConditionalTerm()
1300 {
1301 $conditionalFactors = [];
1302 $conditionalFactors[] = $this->ConditionalFactor();
1303 while ($this->lexer->isNextToken(TokenType::T_AND)) {
1304 $this->match(TokenType::T_AND);
1305 $conditionalFactors[] = $this->ConditionalFactor();
1306 }
1307 // Phase 1 AST optimization: Prevent AST\ConditionalTerm
1308 // if only one AST\ConditionalFactor is defined
1309 if (count($conditionalFactors) === 1) {
1310 return $conditionalFactors[0];
1311 }
1312 return new AST\ConditionalTerm($conditionalFactors);
1313 }
1314 public function ConditionalFactor()
1315 {
1316 $not = \false;
1317 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
1318 $this->match(TokenType::T_NOT);
1319 $not = \true;
1320 }
1321 $conditionalPrimary = $this->ConditionalPrimary();
1322 // Phase 1 AST optimization: Prevent AST\ConditionalFactor
1323 // if only one AST\ConditionalPrimary is defined
1324 if (!$not) {
1325 return $conditionalPrimary;
1326 }
1327 return new AST\ConditionalFactor($conditionalPrimary, $not);
1328 }
1329 public function ConditionalPrimary()
1330 {
1331 $condPrimary = new AST\ConditionalPrimary();
1332 if (!$this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS)) {
1333 $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
1334 return $condPrimary;
1335 }
1336 // Peek beyond the matching closing parenthesis ')'
1337 $peek = $this->peekBeyondClosingParenthesis();
1338 if ($peek !== null && (in_array($peek->value, ['=', '<', '<=', '<>', '>', '>=', '!='], \true) || in_array($peek->type, [TokenType::T_NOT, TokenType::T_BETWEEN, TokenType::T_LIKE, TokenType::T_IN, TokenType::T_IS, TokenType::T_EXISTS], \true) || $this->isMathOperator($peek))) {
1339 $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
1340 return $condPrimary;
1341 }
1342 $this->match(TokenType::T_OPEN_PARENTHESIS);
1343 $condPrimary->conditionalExpression = $this->ConditionalExpression();
1344 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1345 return $condPrimary;
1346 }
1347 public function SimpleConditionalExpression()
1348 {
1349 assert($this->lexer->lookahead !== null);
1350 if ($this->lexer->isNextToken(TokenType::T_EXISTS)) {
1351 return $this->ExistsExpression();
1352 }
1353 $token = $this->lexer->lookahead;
1354 $peek = $this->lexer->glimpse();
1355 $lookahead = $token;
1356 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
1357 $token = $this->lexer->glimpse();
1358 }
1359 assert($token !== null);
1360 assert($peek !== null);
1361 if ($token->type === TokenType::T_IDENTIFIER || $token->type === TokenType::T_INPUT_PARAMETER || $this->isFunction()) {
1362 // Peek beyond the matching closing parenthesis.
1363 $beyond = $this->lexer->peek();
1364 switch ($peek->value) {
1365 case '(':
1366 // Peeks beyond the matched closing parenthesis.
1367 $token = $this->peekBeyondClosingParenthesis(\false);
1368 assert($token !== null);
1369 if ($token->type === TokenType::T_NOT) {
1370 $token = $this->lexer->peek();
1371 assert($token !== null);
1372 }
1373 if ($token->type === TokenType::T_IS) {
1374 $lookahead = $this->lexer->peek();
1375 }
1376 break;
1377 default:
1378 // Peek beyond the PathExpression or InputParameter.
1379 $token = $beyond;
1380 while ($token->value === '.') {
1381 $this->lexer->peek();
1382 $token = $this->lexer->peek();
1383 assert($token !== null);
1384 }
1385 // Also peek beyond a NOT if there is one.
1386 assert($token !== null);
1387 if ($token->type === TokenType::T_NOT) {
1388 $token = $this->lexer->peek();
1389 assert($token !== null);
1390 }
1391 // We need to go even further in case of IS (differentiate between NULL and EMPTY)
1392 $lookahead = $this->lexer->peek();
1393 }
1394 assert($lookahead !== null);
1395 // Also peek beyond a NOT if there is one.
1396 if ($lookahead->type === TokenType::T_NOT) {
1397 $lookahead = $this->lexer->peek();
1398 }
1399 $this->lexer->resetPeek();
1400 }
1401 if ($token->type === TokenType::T_BETWEEN) {
1402 return $this->BetweenExpression();
1403 }
1404 if ($token->type === TokenType::T_LIKE) {
1405 return $this->LikeExpression();
1406 }
1407 if ($token->type === TokenType::T_IN) {
1408 return $this->InExpression();
1409 }
1410 if ($token->type === TokenType::T_INSTANCE) {
1411 return $this->InstanceOfExpression();
1412 }
1413 if ($token->type === TokenType::T_MEMBER) {
1414 return $this->CollectionMemberExpression();
1415 }
1416 assert($lookahead !== null);
1417 if ($token->type === TokenType::T_IS && $lookahead->type === TokenType::T_NULL) {
1418 return $this->NullComparisonExpression();
1419 }
1420 if ($token->type === TokenType::T_IS && $lookahead->type === TokenType::T_EMPTY) {
1421 return $this->EmptyCollectionComparisonExpression();
1422 }
1423 return $this->ComparisonExpression();
1424 }
1425 public function EmptyCollectionComparisonExpression()
1426 {
1427 $pathExpression = $this->CollectionValuedPathExpression();
1428 $this->match(TokenType::T_IS);
1429 $not = \false;
1430 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
1431 $this->match(TokenType::T_NOT);
1432 $not = \true;
1433 }
1434 $this->match(TokenType::T_EMPTY);
1435 return new AST\EmptyCollectionComparisonExpression($pathExpression, $not);
1436 }
1437 public function CollectionMemberExpression()
1438 {
1439 $not = \false;
1440 $entityExpr = $this->EntityExpression();
1441 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
1442 $this->match(TokenType::T_NOT);
1443 $not = \true;
1444 }
1445 $this->match(TokenType::T_MEMBER);
1446 if ($this->lexer->isNextToken(TokenType::T_OF)) {
1447 $this->match(TokenType::T_OF);
1448 }
1449 return new AST\CollectionMemberExpression($entityExpr, $this->CollectionValuedPathExpression(), $not);
1450 }
1451 public function Literal()
1452 {
1453 assert($this->lexer->lookahead !== null);
1454 assert($this->lexer->token !== null);
1455 switch ($this->lexer->lookahead->type) {
1456 case TokenType::T_STRING:
1457 $this->match(TokenType::T_STRING);
1458 return new AST\Literal(AST\Literal::STRING, $this->lexer->token->value);
1459 case TokenType::T_INTEGER:
1460 case TokenType::T_FLOAT:
1461 $this->match($this->lexer->isNextToken(TokenType::T_INTEGER) ? TokenType::T_INTEGER : TokenType::T_FLOAT);
1462 return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token->value);
1463 case TokenType::T_TRUE:
1464 case TokenType::T_FALSE:
1465 $this->match($this->lexer->isNextToken(TokenType::T_TRUE) ? TokenType::T_TRUE : TokenType::T_FALSE);
1466 return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token->value);
1467 default:
1468 $this->syntaxError('Literal');
1469 }
1470 }
1471 public function InParameter()
1472 {
1473 assert($this->lexer->lookahead !== null);
1474 if ($this->lexer->lookahead->type === TokenType::T_INPUT_PARAMETER) {
1475 return $this->InputParameter();
1476 }
1477 return $this->ArithmeticExpression();
1478 }
1479 public function InputParameter()
1480 {
1481 $this->match(TokenType::T_INPUT_PARAMETER);
1482 assert($this->lexer->token !== null);
1483 return new AST\InputParameter($this->lexer->token->value);
1484 }
1485 public function ArithmeticExpression()
1486 {
1487 $expr = new AST\ArithmeticExpression();
1488 if ($this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS)) {
1489 $peek = $this->lexer->glimpse();
1490 assert($peek !== null);
1491 if ($peek->type === TokenType::T_SELECT) {
1492 $this->match(TokenType::T_OPEN_PARENTHESIS);
1493 $expr->subselect = $this->Subselect();
1494 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1495 return $expr;
1496 }
1497 }
1498 $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
1499 return $expr;
1500 }
1501 public function SimpleArithmeticExpression()
1502 {
1503 $terms = [];
1504 $terms[] = $this->ArithmeticTerm();
1505 while (($isPlus = $this->lexer->isNextToken(TokenType::T_PLUS)) || $this->lexer->isNextToken(TokenType::T_MINUS)) {
1506 $this->match($isPlus ? TokenType::T_PLUS : TokenType::T_MINUS);
1507 assert($this->lexer->token !== null);
1508 $terms[] = $this->lexer->token->value;
1509 $terms[] = $this->ArithmeticTerm();
1510 }
1511 // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
1512 // if only one AST\ArithmeticTerm is defined
1513 if (count($terms) === 1) {
1514 return $terms[0];
1515 }
1516 return new AST\SimpleArithmeticExpression($terms);
1517 }
1518 public function ArithmeticTerm()
1519 {
1520 $factors = [];
1521 $factors[] = $this->ArithmeticFactor();
1522 while (($isMult = $this->lexer->isNextToken(TokenType::T_MULTIPLY)) || $this->lexer->isNextToken(TokenType::T_DIVIDE)) {
1523 $this->match($isMult ? TokenType::T_MULTIPLY : TokenType::T_DIVIDE);
1524 assert($this->lexer->token !== null);
1525 $factors[] = $this->lexer->token->value;
1526 $factors[] = $this->ArithmeticFactor();
1527 }
1528 // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
1529 // if only one AST\ArithmeticFactor is defined
1530 if (count($factors) === 1) {
1531 return $factors[0];
1532 }
1533 return new AST\ArithmeticTerm($factors);
1534 }
1535 public function ArithmeticFactor()
1536 {
1537 $sign = null;
1538 $isPlus = $this->lexer->isNextToken(TokenType::T_PLUS);
1539 if ($isPlus || $this->lexer->isNextToken(TokenType::T_MINUS)) {
1540 $this->match($isPlus ? TokenType::T_PLUS : TokenType::T_MINUS);
1541 $sign = $isPlus;
1542 }
1543 $primary = $this->ArithmeticPrimary();
1544 // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
1545 // if only one AST\ArithmeticPrimary is defined
1546 if ($sign === null) {
1547 return $primary;
1548 }
1549 return new AST\ArithmeticFactor($primary, $sign);
1550 }
1551 public function ArithmeticPrimary()
1552 {
1553 if ($this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS)) {
1554 $this->match(TokenType::T_OPEN_PARENTHESIS);
1555 $expr = $this->SimpleArithmeticExpression();
1556 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1557 return new AST\ParenthesisExpression($expr);
1558 }
1559 if ($this->lexer->lookahead === null) {
1560 $this->syntaxError('ArithmeticPrimary');
1561 }
1562 switch ($this->lexer->lookahead->type) {
1563 case TokenType::T_COALESCE:
1564 case TokenType::T_NULLIF:
1565 case TokenType::T_CASE:
1566 return $this->CaseExpression();
1567 case TokenType::T_IDENTIFIER:
1568 $peek = $this->lexer->glimpse();
1569 if ($peek !== null && $peek->value === '(') {
1570 return $this->FunctionDeclaration();
1571 }
1572 if ($peek !== null && $peek->value === '.') {
1573 return $this->SingleValuedPathExpression();
1574 }
1575 if (isset($this->queryComponents[$this->lexer->lookahead->value]['resultVariable'])) {
1576 return $this->ResultVariable();
1577 }
1578 return $this->StateFieldPathExpression();
1579 case TokenType::T_INPUT_PARAMETER:
1580 return $this->InputParameter();
1581 default:
1582 $peek = $this->lexer->glimpse();
1583 if ($peek !== null && $peek->value === '(') {
1584 return $this->FunctionDeclaration();
1585 }
1586 return $this->Literal();
1587 }
1588 }
1589 public function StringExpression()
1590 {
1591 $peek = $this->lexer->glimpse();
1592 assert($peek !== null);
1593 // Subselect
1594 if ($this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS) && $peek->type === TokenType::T_SELECT) {
1595 $this->match(TokenType::T_OPEN_PARENTHESIS);
1596 $expr = $this->Subselect();
1597 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1598 return $expr;
1599 }
1600 assert($this->lexer->lookahead !== null);
1601 // ResultVariable (string)
1602 if ($this->lexer->isNextToken(TokenType::T_IDENTIFIER) && isset($this->queryComponents[$this->lexer->lookahead->value]['resultVariable'])) {
1603 return $this->ResultVariable();
1604 }
1605 return $this->StringPrimary();
1606 }
1607 public function StringPrimary()
1608 {
1609 assert($this->lexer->lookahead !== null);
1610 $lookaheadType = $this->lexer->lookahead->type;
1611 switch ($lookaheadType) {
1612 case TokenType::T_IDENTIFIER:
1613 $peek = $this->lexer->glimpse();
1614 assert($peek !== null);
1615 if ($peek->value === '.') {
1616 return $this->StateFieldPathExpression();
1617 }
1618 if ($peek->value === '(') {
1619 // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions.
1620 return $this->FunctionDeclaration();
1621 }
1622 $this->syntaxError("'.' or '('");
1623 break;
1624 case TokenType::T_STRING:
1625 $this->match(TokenType::T_STRING);
1626 assert($this->lexer->token !== null);
1627 return new AST\Literal(AST\Literal::STRING, $this->lexer->token->value);
1628 case TokenType::T_INPUT_PARAMETER:
1629 return $this->InputParameter();
1630 case TokenType::T_CASE:
1631 case TokenType::T_COALESCE:
1632 case TokenType::T_NULLIF:
1633 return $this->CaseExpression();
1634 default:
1635 assert($lookaheadType !== null);
1636 if ($this->isAggregateFunction($lookaheadType)) {
1637 return $this->AggregateExpression();
1638 }
1639 }
1640 $this->syntaxError('StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression');
1641 }
1642 public function EntityExpression()
1643 {
1644 $glimpse = $this->lexer->glimpse();
1645 assert($glimpse !== null);
1646 if ($this->lexer->isNextToken(TokenType::T_IDENTIFIER) && $glimpse->value === '.') {
1647 return $this->SingleValuedAssociationPathExpression();
1648 }
1649 return $this->SimpleEntityExpression();
1650 }
1651 public function SimpleEntityExpression()
1652 {
1653 if ($this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER)) {
1654 return $this->InputParameter();
1655 }
1656 return $this->StateFieldPathExpression();
1657 }
1658 public function AggregateExpression()
1659 {
1660 assert($this->lexer->lookahead !== null);
1661 $lookaheadType = $this->lexer->lookahead->type;
1662 $isDistinct = \false;
1663 if (!in_array($lookaheadType, [TokenType::T_COUNT, TokenType::T_AVG, TokenType::T_MAX, TokenType::T_MIN, TokenType::T_SUM], \true)) {
1664 $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
1665 }
1666 $this->match($lookaheadType);
1667 assert($this->lexer->token !== null);
1668 $functionName = $this->lexer->token->value;
1669 $this->match(TokenType::T_OPEN_PARENTHESIS);
1670 if ($this->lexer->isNextToken(TokenType::T_DISTINCT)) {
1671 $this->match(TokenType::T_DISTINCT);
1672 $isDistinct = \true;
1673 }
1674 $pathExp = $this->SimpleArithmeticExpression();
1675 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1676 return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
1677 }
1678 public function QuantifiedExpression()
1679 {
1680 assert($this->lexer->lookahead !== null);
1681 $lookaheadType = $this->lexer->lookahead->type;
1682 $value = $this->lexer->lookahead->value;
1683 if (!in_array($lookaheadType, [TokenType::T_ALL, TokenType::T_ANY, TokenType::T_SOME], \true)) {
1684 $this->syntaxError('ALL, ANY or SOME');
1685 }
1686 $this->match($lookaheadType);
1687 $this->match(TokenType::T_OPEN_PARENTHESIS);
1688 $qExpr = new AST\QuantifiedExpression($this->Subselect());
1689 $qExpr->type = $value;
1690 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1691 return $qExpr;
1692 }
1693 public function BetweenExpression()
1694 {
1695 $not = \false;
1696 $arithExpr1 = $this->ArithmeticExpression();
1697 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
1698 $this->match(TokenType::T_NOT);
1699 $not = \true;
1700 }
1701 $this->match(TokenType::T_BETWEEN);
1702 $arithExpr2 = $this->ArithmeticExpression();
1703 $this->match(TokenType::T_AND);
1704 $arithExpr3 = $this->ArithmeticExpression();
1705 return new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3, $not);
1706 }
1707 public function ComparisonExpression()
1708 {
1709 $this->lexer->glimpse();
1710 $leftExpr = $this->ArithmeticExpression();
1711 $operator = $this->ComparisonOperator();
1712 $rightExpr = $this->isNextAllAnySome() ? $this->QuantifiedExpression() : $this->ArithmeticExpression();
1713 return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
1714 }
1715 public function InExpression()
1716 {
1717 $expression = $this->ArithmeticExpression();
1718 $not = \false;
1719 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
1720 $this->match(TokenType::T_NOT);
1721 $not = \true;
1722 }
1723 $this->match(TokenType::T_IN);
1724 $this->match(TokenType::T_OPEN_PARENTHESIS);
1725 if ($this->lexer->isNextToken(TokenType::T_SELECT)) {
1726 $inExpression = new AST\InSubselectExpression($expression, $this->Subselect(), $not);
1727 } else {
1728 $literals = [$this->InParameter()];
1729 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
1730 $this->match(TokenType::T_COMMA);
1731 $literals[] = $this->InParameter();
1732 }
1733 $inExpression = new AST\InListExpression($expression, $literals, $not);
1734 }
1735 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1736 return $inExpression;
1737 }
1738 public function InstanceOfExpression()
1739 {
1740 $identificationVariable = $this->IdentificationVariable();
1741 $not = \false;
1742 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
1743 $this->match(TokenType::T_NOT);
1744 $not = \true;
1745 }
1746 $this->match(TokenType::T_INSTANCE);
1747 $this->match(TokenType::T_OF);
1748 $exprValues = $this->lexer->isNextToken(TokenType::T_OPEN_PARENTHESIS) ? $this->InstanceOfParameterList() : [$this->InstanceOfParameter()];
1749 return new AST\InstanceOfExpression($identificationVariable, $exprValues, $not);
1750 }
1751 public function InstanceOfParameterList() : array
1752 {
1753 $this->match(TokenType::T_OPEN_PARENTHESIS);
1754 $exprValues = [$this->InstanceOfParameter()];
1755 while ($this->lexer->isNextToken(TokenType::T_COMMA)) {
1756 $this->match(TokenType::T_COMMA);
1757 $exprValues[] = $this->InstanceOfParameter();
1758 }
1759 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1760 return $exprValues;
1761 }
1762 public function InstanceOfParameter()
1763 {
1764 if ($this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER)) {
1765 $this->match(TokenType::T_INPUT_PARAMETER);
1766 assert($this->lexer->token !== null);
1767 return new AST\InputParameter($this->lexer->token->value);
1768 }
1769 $abstractSchemaName = $this->AbstractSchemaName();
1770 $this->validateAbstractSchemaName($abstractSchemaName);
1771 return $abstractSchemaName;
1772 }
1773 public function LikeExpression()
1774 {
1775 $stringExpr = $this->StringExpression();
1776 $not = \false;
1777 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
1778 $this->match(TokenType::T_NOT);
1779 $not = \true;
1780 }
1781 $this->match(TokenType::T_LIKE);
1782 if ($this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER)) {
1783 $this->match(TokenType::T_INPUT_PARAMETER);
1784 assert($this->lexer->token !== null);
1785 $stringPattern = new AST\InputParameter($this->lexer->token->value);
1786 } else {
1787 $stringPattern = $this->StringPrimary();
1788 }
1789 $escapeChar = null;
1790 if ($this->lexer->lookahead !== null && $this->lexer->lookahead->type === TokenType::T_ESCAPE) {
1791 $this->match(TokenType::T_ESCAPE);
1792 $this->match(TokenType::T_STRING);
1793 assert($this->lexer->token !== null);
1794 $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token->value);
1795 }
1796 return new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar, $not);
1797 }
1798 public function NullComparisonExpression()
1799 {
1800 switch (\true) {
1801 case $this->lexer->isNextToken(TokenType::T_INPUT_PARAMETER):
1802 $this->match(TokenType::T_INPUT_PARAMETER);
1803 assert($this->lexer->token !== null);
1804 $expr = new AST\InputParameter($this->lexer->token->value);
1805 break;
1806 case $this->lexer->isNextToken(TokenType::T_NULLIF):
1807 $expr = $this->NullIfExpression();
1808 break;
1809 case $this->lexer->isNextToken(TokenType::T_COALESCE):
1810 $expr = $this->CoalesceExpression();
1811 break;
1812 case $this->isFunction():
1813 $expr = $this->FunctionDeclaration();
1814 break;
1815 default:
1816 // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
1817 $glimpse = $this->lexer->glimpse();
1818 assert($glimpse !== null);
1819 if ($glimpse->type === TokenType::T_DOT) {
1820 $expr = $this->SingleValuedPathExpression();
1821 // Leave switch statement
1822 break;
1823 }
1824 assert($this->lexer->lookahead !== null);
1825 $lookaheadValue = $this->lexer->lookahead->value;
1826 // Validate existing component
1827 if (!isset($this->queryComponents[$lookaheadValue])) {
1828 $this->semanticalError('Cannot add having condition on undefined result variable.');
1829 }
1830 // Validate SingleValuedPathExpression (ie.: "product")
1831 if (isset($this->queryComponents[$lookaheadValue]['metadata'])) {
1832 $expr = $this->SingleValuedPathExpression();
1833 break;
1834 }
1835 // Validating ResultVariable
1836 if (!isset($this->queryComponents[$lookaheadValue]['resultVariable'])) {
1837 $this->semanticalError('Cannot add having condition on a non result variable.');
1838 }
1839 $expr = $this->ResultVariable();
1840 break;
1841 }
1842 $this->match(TokenType::T_IS);
1843 $not = \false;
1844 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
1845 $this->match(TokenType::T_NOT);
1846 $not = \true;
1847 }
1848 $this->match(TokenType::T_NULL);
1849 return new AST\NullComparisonExpression($expr, $not);
1850 }
1851 public function ExistsExpression()
1852 {
1853 $not = \false;
1854 if ($this->lexer->isNextToken(TokenType::T_NOT)) {
1855 $this->match(TokenType::T_NOT);
1856 $not = \true;
1857 }
1858 $this->match(TokenType::T_EXISTS);
1859 $this->match(TokenType::T_OPEN_PARENTHESIS);
1860 $subselect = $this->Subselect();
1861 $this->match(TokenType::T_CLOSE_PARENTHESIS);
1862 return new AST\ExistsExpression($subselect, $not);
1863 }
1864 public function ComparisonOperator()
1865 {
1866 assert($this->lexer->lookahead !== null);
1867 switch ($this->lexer->lookahead->value) {
1868 case '=':
1869 $this->match(TokenType::T_EQUALS);
1870 return '=';
1871 case '<':
1872 $this->match(TokenType::T_LOWER_THAN);
1873 $operator = '<';
1874 if ($this->lexer->isNextToken(TokenType::T_EQUALS)) {
1875 $this->match(TokenType::T_EQUALS);
1876 $operator .= '=';
1877 } elseif ($this->lexer->isNextToken(TokenType::T_GREATER_THAN)) {
1878 $this->match(TokenType::T_GREATER_THAN);
1879 $operator .= '>';
1880 }
1881 return $operator;
1882 case '>':
1883 $this->match(TokenType::T_GREATER_THAN);
1884 $operator = '>';
1885 if ($this->lexer->isNextToken(TokenType::T_EQUALS)) {
1886 $this->match(TokenType::T_EQUALS);
1887 $operator .= '=';
1888 }
1889 return $operator;
1890 case '!':
1891 $this->match(TokenType::T_NEGATE);
1892 $this->match(TokenType::T_EQUALS);
1893 return '<>';
1894 default:
1895 $this->syntaxError('=, <, <=, <>, >, >=, !=');
1896 }
1897 }
1898 public function FunctionDeclaration()
1899 {
1900 assert($this->lexer->lookahead !== null);
1901 $token = $this->lexer->lookahead;
1902 $funcName = strtolower($token->value);
1903 $customFunctionDeclaration = $this->CustomFunctionDeclaration();
1904 // Check for custom functions functions first!
1905 switch (\true) {
1906 case $customFunctionDeclaration !== null:
1907 return $customFunctionDeclaration;
1908 case isset(self::$stringFunctions[$funcName]):
1909 return $this->FunctionsReturningStrings();
1910 case isset(self::$numericFunctions[$funcName]):
1911 return $this->FunctionsReturningNumerics();
1912 case isset(self::$datetimeFunctions[$funcName]):
1913 return $this->FunctionsReturningDatetime();
1914 default:
1915 $this->syntaxError('known function', $token);
1916 }
1917 }
1918 private function CustomFunctionDeclaration() : ?Functions\FunctionNode
1919 {
1920 assert($this->lexer->lookahead !== null);
1921 $token = $this->lexer->lookahead;
1922 $funcName = strtolower($token->value);
1923 // Check for custom functions afterwards
1924 $config = $this->em->getConfiguration();
1925 switch (\true) {
1926 case $config->getCustomStringFunction($funcName) !== null:
1927 return $this->CustomFunctionsReturningStrings();
1928 case $config->getCustomNumericFunction($funcName) !== null:
1929 return $this->CustomFunctionsReturningNumerics();
1930 case $config->getCustomDatetimeFunction($funcName) !== null:
1931 return $this->CustomFunctionsReturningDatetime();
1932 default:
1933 return null;
1934 }
1935 }
1936 public function FunctionsReturningNumerics()
1937 {
1938 assert($this->lexer->lookahead !== null);
1939 $funcNameLower = strtolower($this->lexer->lookahead->value);
1940 $funcClass = self::$numericFunctions[$funcNameLower];
1941 $function = new $funcClass($funcNameLower);
1942 $function->parse($this);
1943 return $function;
1944 }
1945 public function CustomFunctionsReturningNumerics()
1946 {
1947 assert($this->lexer->lookahead !== null);
1948 // getCustomNumericFunction is case-insensitive
1949 $functionName = strtolower($this->lexer->lookahead->value);
1950 $functionClass = $this->em->getConfiguration()->getCustomNumericFunction($functionName);
1951 assert($functionClass !== null);
1952 $function = is_string($functionClass) ? new $functionClass($functionName) : $functionClass($functionName);
1953 $function->parse($this);
1954 return $function;
1955 }
1956 public function FunctionsReturningDatetime()
1957 {
1958 assert($this->lexer->lookahead !== null);
1959 $funcNameLower = strtolower($this->lexer->lookahead->value);
1960 $funcClass = self::$datetimeFunctions[$funcNameLower];
1961 $function = new $funcClass($funcNameLower);
1962 $function->parse($this);
1963 return $function;
1964 }
1965 public function CustomFunctionsReturningDatetime()
1966 {
1967 assert($this->lexer->lookahead !== null);
1968 // getCustomDatetimeFunction is case-insensitive
1969 $functionName = $this->lexer->lookahead->value;
1970 $functionClass = $this->em->getConfiguration()->getCustomDatetimeFunction($functionName);
1971 assert($functionClass !== null);
1972 $function = is_string($functionClass) ? new $functionClass($functionName) : $functionClass($functionName);
1973 $function->parse($this);
1974 return $function;
1975 }
1976 public function FunctionsReturningStrings()
1977 {
1978 assert($this->lexer->lookahead !== null);
1979 $funcNameLower = strtolower($this->lexer->lookahead->value);
1980 $funcClass = self::$stringFunctions[$funcNameLower];
1981 $function = new $funcClass($funcNameLower);
1982 $function->parse($this);
1983 return $function;
1984 }
1985 public function CustomFunctionsReturningStrings()
1986 {
1987 assert($this->lexer->lookahead !== null);
1988 // getCustomStringFunction is case-insensitive
1989 $functionName = $this->lexer->lookahead->value;
1990 $functionClass = $this->em->getConfiguration()->getCustomStringFunction($functionName);
1991 assert($functionClass !== null);
1992 $function = is_string($functionClass) ? new $functionClass($functionName) : $functionClass($functionName);
1993 $function->parse($this);
1994 return $function;
1995 }
1996 private function getMetadataForDqlAlias(string $dqlAlias) : ClassMetadata
1997 {
1998 if (!isset($this->queryComponents[$dqlAlias]['metadata'])) {
1999 throw new LogicException(sprintf('No metadata for DQL alias: %s', $dqlAlias));
2000 }
2001 return $this->queryComponents[$dqlAlias]['metadata'];
2002 }
2003 }
2004