Complex classes like Parser often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Parser, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
37 | class Parser |
||
38 | { |
||
39 | /** |
||
40 | * READ-ONLY: Maps BUILT-IN string function names to AST class names. |
||
41 | * |
||
42 | * @var array |
||
43 | */ |
||
44 | private static $_STRING_FUNCTIONS = [ |
||
45 | 'concat' => Functions\ConcatFunction::class, |
||
46 | 'substring' => Functions\SubstringFunction::class, |
||
47 | 'trim' => Functions\TrimFunction::class, |
||
48 | 'lower' => Functions\LowerFunction::class, |
||
49 | 'upper' => Functions\UpperFunction::class, |
||
50 | 'identity' => Functions\IdentityFunction::class, |
||
51 | ]; |
||
52 | |||
53 | /** |
||
54 | * READ-ONLY: Maps BUILT-IN numeric function names to AST class names. |
||
55 | * |
||
56 | * @var array |
||
57 | */ |
||
58 | private static $_NUMERIC_FUNCTIONS = [ |
||
59 | 'length' => Functions\LengthFunction::class, |
||
60 | 'locate' => Functions\LocateFunction::class, |
||
61 | 'abs' => Functions\AbsFunction::class, |
||
62 | 'sqrt' => Functions\SqrtFunction::class, |
||
63 | 'mod' => Functions\ModFunction::class, |
||
64 | 'size' => Functions\SizeFunction::class, |
||
65 | 'date_diff' => Functions\DateDiffFunction::class, |
||
66 | 'bit_and' => Functions\BitAndFunction::class, |
||
67 | 'bit_or' => Functions\BitOrFunction::class, |
||
68 | |||
69 | // Aggregate functions |
||
70 | 'min' => Functions\MinFunction::class, |
||
71 | 'max' => Functions\MaxFunction::class, |
||
72 | 'avg' => Functions\AvgFunction::class, |
||
73 | 'sum' => Functions\SumFunction::class, |
||
74 | 'count' => Functions\CountFunction::class, |
||
75 | ]; |
||
76 | |||
77 | /** |
||
78 | * READ-ONLY: Maps BUILT-IN datetime function names to AST class names. |
||
79 | * |
||
80 | * @var array |
||
81 | */ |
||
82 | private static $_DATETIME_FUNCTIONS = [ |
||
83 | 'current_date' => Functions\CurrentDateFunction::class, |
||
84 | 'current_time' => Functions\CurrentTimeFunction::class, |
||
85 | 'current_timestamp' => Functions\CurrentTimestampFunction::class, |
||
86 | 'date_add' => Functions\DateAddFunction::class, |
||
87 | 'date_sub' => Functions\DateSubFunction::class, |
||
88 | ]; |
||
89 | |||
90 | /* |
||
91 | * Expressions that were encountered during parsing of identifiers and expressions |
||
92 | * and still need to be validated. |
||
93 | */ |
||
94 | |||
95 | /** |
||
96 | * @var array |
||
97 | */ |
||
98 | private $deferredIdentificationVariables = []; |
||
99 | |||
100 | /** |
||
101 | * @var array |
||
102 | */ |
||
103 | private $deferredPartialObjectExpressions = []; |
||
104 | |||
105 | /** |
||
106 | * @var array |
||
107 | */ |
||
108 | private $deferredPathExpressions = []; |
||
109 | |||
110 | /** |
||
111 | * @var array |
||
112 | */ |
||
113 | private $deferredResultVariables = []; |
||
114 | |||
115 | /** |
||
116 | * @var array |
||
117 | */ |
||
118 | private $deferredNewObjectExpressions = []; |
||
119 | |||
120 | /** |
||
121 | * The lexer. |
||
122 | * |
||
123 | * @var \Doctrine\ORM\Query\Lexer |
||
124 | */ |
||
125 | private $lexer; |
||
126 | |||
127 | /** |
||
128 | * The parser result. |
||
129 | * |
||
130 | * @var \Doctrine\ORM\Query\ParserResult |
||
131 | */ |
||
132 | private $parserResult; |
||
133 | |||
134 | /** |
||
135 | * The EntityManager. |
||
136 | * |
||
137 | * @var \Doctrine\ORM\EntityManager |
||
138 | */ |
||
139 | private $em; |
||
140 | |||
141 | /** |
||
142 | * The Query to parse. |
||
143 | * |
||
144 | * @var Query |
||
145 | */ |
||
146 | private $query; |
||
147 | |||
148 | /** |
||
149 | * Map of declared query components in the parsed query. |
||
150 | * |
||
151 | * @var array |
||
152 | */ |
||
153 | private $queryComponents = []; |
||
154 | |||
155 | /** |
||
156 | * Keeps the nesting level of defined ResultVariables. |
||
157 | * |
||
158 | * @var integer |
||
159 | */ |
||
160 | private $nestingLevel = 0; |
||
161 | |||
162 | /** |
||
163 | * Any additional custom tree walkers that modify the AST. |
||
164 | * |
||
165 | * @var array |
||
166 | */ |
||
167 | private $customTreeWalkers = []; |
||
168 | |||
169 | /** |
||
170 | * The custom last tree walker, if any, that is responsible for producing the output. |
||
171 | * |
||
172 | * @var TreeWalker |
||
173 | */ |
||
174 | private $customOutputWalker; |
||
175 | |||
176 | /** |
||
177 | * @var array |
||
178 | */ |
||
179 | private $identVariableExpressions = []; |
||
180 | |||
181 | /** |
||
182 | * Creates a new query parser object. |
||
183 | * |
||
184 | * @param Query $query The Query to parse. |
||
185 | */ |
||
186 | 757 | public function __construct(Query $query) |
|
193 | |||
194 | /** |
||
195 | * Sets a custom tree walker that produces output. |
||
196 | * This tree walker will be run last over the AST, after any other walkers. |
||
197 | * |
||
198 | * @param string $className |
||
199 | * |
||
200 | * @return void |
||
201 | */ |
||
202 | 111 | public function setCustomOutputTreeWalker($className) |
|
206 | |||
207 | /** |
||
208 | * Adds a custom tree walker for modifying the AST. |
||
209 | * |
||
210 | * @param string $className |
||
211 | * |
||
212 | * @return void |
||
213 | */ |
||
214 | public function addCustomTreeWalker($className) |
||
218 | |||
219 | /** |
||
220 | * Gets the lexer used by the parser. |
||
221 | * |
||
222 | * @return \Doctrine\ORM\Query\Lexer |
||
223 | */ |
||
224 | 27 | public function getLexer() |
|
228 | |||
229 | /** |
||
230 | * Gets the ParserResult that is being filled with information during parsing. |
||
231 | * |
||
232 | * @return \Doctrine\ORM\Query\ParserResult |
||
233 | */ |
||
234 | public function getParserResult() |
||
238 | |||
239 | /** |
||
240 | * Gets the EntityManager used by the parser. |
||
241 | * |
||
242 | * @return \Doctrine\ORM\EntityManager |
||
243 | */ |
||
244 | public function getEntityManager() |
||
248 | |||
249 | /** |
||
250 | * Parses and builds AST for the given Query. |
||
251 | * |
||
252 | * @return \Doctrine\ORM\Query\AST\SelectStatement | |
||
253 | * \Doctrine\ORM\Query\AST\UpdateStatement | |
||
254 | * \Doctrine\ORM\Query\AST\DeleteStatement |
||
255 | */ |
||
256 | 757 | public function getAST() |
|
288 | |||
289 | /** |
||
290 | * Attempts to match the given token with the current lookahead token. |
||
291 | * |
||
292 | * If they match, updates the lookahead token; otherwise raises a syntax |
||
293 | * error. |
||
294 | * |
||
295 | * @param int $token The token type. |
||
296 | * |
||
297 | * @return void |
||
298 | * |
||
299 | * @throws QueryException If the tokens don't match. |
||
300 | */ |
||
301 | 768 | public function match($token) |
|
325 | |||
326 | /** |
||
327 | * Frees this parser, enabling it to be reused. |
||
328 | * |
||
329 | * @param boolean $deep Whether to clean peek and reset errors. |
||
330 | * @param integer $position Position to reset. |
||
331 | * |
||
332 | * @return void |
||
333 | */ |
||
334 | public function free($deep = false, $position = 0) |
||
347 | |||
348 | /** |
||
349 | * Parses a query string. |
||
350 | * |
||
351 | * @return ParserResult |
||
352 | */ |
||
353 | 757 | public function parse() |
|
398 | |||
399 | /** |
||
400 | * Fixes order of identification variables. |
||
401 | * |
||
402 | * They have to appear in the select clause in the same order as the |
||
403 | * declarations (from ... x join ... y join ... z ...) appear in the query |
||
404 | * as the hydration process relies on that order for proper operation. |
||
405 | * |
||
406 | * @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST |
||
407 | * |
||
408 | * @return void |
||
409 | */ |
||
410 | 697 | private function fixIdentificationVariableOrder($AST) |
|
429 | |||
430 | /** |
||
431 | * Generates a new syntax error. |
||
432 | * |
||
433 | * @param string $expected Expected string. |
||
434 | * @param array|null $token Got token. |
||
435 | * |
||
436 | * @return void |
||
437 | * |
||
438 | * @throws \Doctrine\ORM\Query\QueryException |
||
439 | */ |
||
440 | 18 | public function syntaxError($expected = '', $token = null) |
|
454 | |||
455 | /** |
||
456 | * Generates a new semantical error. |
||
457 | * |
||
458 | * @param string $message Optional message. |
||
459 | * @param array|null $token Optional token. |
||
460 | * |
||
461 | * @return void |
||
462 | * |
||
463 | * @throws \Doctrine\ORM\Query\QueryException |
||
464 | */ |
||
465 | 29 | public function semanticalError($message = '', $token = null) |
|
489 | |||
490 | /** |
||
491 | * Peeks beyond the matched closing parenthesis and returns the first token after that one. |
||
492 | * |
||
493 | * @param boolean $resetPeek Reset peek after finding the closing parenthesis. |
||
494 | * |
||
495 | * @return array |
||
496 | */ |
||
497 | 91 | private function peekBeyondClosingParenthesis($resetPeek = true) |
|
525 | |||
526 | /** |
||
527 | * Checks if the given token indicates a mathematical operator. |
||
528 | * |
||
529 | * @param array $token |
||
530 | * |
||
531 | * @return boolean TRUE if the token is a mathematical operator, FALSE otherwise. |
||
532 | */ |
||
533 | 277 | private function isMathOperator($token) |
|
537 | |||
538 | /** |
||
539 | * Checks if the next-next (after lookahead) token starts a function. |
||
540 | * |
||
541 | * @return boolean TRUE if the next-next tokens start a function, FALSE otherwise. |
||
542 | */ |
||
543 | 319 | private function isFunction() |
|
552 | |||
553 | /** |
||
554 | * Checks whether the given token type indicates an aggregate function. |
||
555 | * |
||
556 | * @param int $tokenType |
||
557 | * |
||
558 | * @return boolean TRUE if the token type is an aggregate function, FALSE otherwise. |
||
559 | */ |
||
560 | 1 | private function isAggregateFunction($tokenType) |
|
564 | |||
565 | /** |
||
566 | * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME. |
||
567 | * |
||
568 | * @return boolean |
||
569 | */ |
||
570 | 259 | private function isNextAllAnySome() |
|
574 | |||
575 | /** |
||
576 | * Validates that the given <tt>IdentificationVariable</tt> is semantically correct. |
||
577 | * It must exist in query components list. |
||
578 | * |
||
579 | * @return void |
||
580 | */ |
||
581 | 708 | private function processDeferredIdentificationVariables() |
|
582 | { |
||
583 | 708 | foreach ($this->deferredIdentificationVariables as $deferredItem) { |
|
584 | 696 | $identVariable = $deferredItem['expression']; |
|
585 | |||
586 | // Check if IdentificationVariable exists in queryComponents |
||
587 | 696 | if ( ! isset($this->queryComponents[$identVariable])) { |
|
588 | 1 | $this->semanticalError( |
|
589 | 1 | "'$identVariable' is not defined.", $deferredItem['token'] |
|
590 | ); |
||
591 | } |
||
592 | |||
593 | 696 | $qComp = $this->queryComponents[$identVariable]; |
|
594 | |||
595 | // Check if queryComponent points to an AbstractSchemaName or a ResultVariable |
||
596 | 696 | if ( ! isset($qComp['metadata'])) { |
|
597 | $this->semanticalError( |
||
598 | "'$identVariable' does not point to a Class.", $deferredItem['token'] |
||
599 | ); |
||
600 | } |
||
601 | |||
602 | // Validate if identification variable nesting level is lower or equal than the current one |
||
603 | 696 | if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) { |
|
604 | $this->semanticalError( |
||
605 | 696 | "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token'] |
|
606 | ); |
||
607 | } |
||
608 | } |
||
609 | 707 | } |
|
610 | |||
611 | /** |
||
612 | * Validates that the given <tt>NewObjectExpression</tt>. |
||
613 | * |
||
614 | * @param \Doctrine\ORM\Query\AST\SelectClause $AST |
||
615 | * |
||
616 | * @return void |
||
617 | */ |
||
618 | 26 | private function processDeferredNewObjectExpressions($AST) |
|
659 | |||
660 | /** |
||
661 | * Validates that the given <tt>PartialObjectExpression</tt> is semantically correct. |
||
662 | * It must exist in query components list. |
||
663 | * |
||
664 | * @return void |
||
665 | */ |
||
666 | 11 | private function processDeferredPartialObjectExpressions() |
|
696 | |||
697 | /** |
||
698 | * Validates that the given <tt>ResultVariable</tt> is semantically correct. |
||
699 | * It must exist in query components list. |
||
700 | * |
||
701 | * @return void |
||
702 | */ |
||
703 | 9 | private function processDeferredResultVariables() |
|
732 | |||
733 | /** |
||
734 | * Validates that the given <tt>PathExpression</tt> is semantically correct for grammar rules: |
||
735 | * |
||
736 | * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression |
||
737 | * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression |
||
738 | * StateFieldPathExpression ::= IdentificationVariable "." StateField |
||
739 | * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField |
||
740 | * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField |
||
741 | * |
||
742 | * @return void |
||
743 | */ |
||
744 | 511 | private function processDeferredPathExpressions() |
|
809 | |||
810 | /** |
||
811 | * @return void |
||
812 | */ |
||
813 | 698 | private function processRootEntityAliasSelected() |
|
827 | |||
828 | /** |
||
829 | * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement |
||
830 | * |
||
831 | * @return \Doctrine\ORM\Query\AST\SelectStatement | |
||
832 | * \Doctrine\ORM\Query\AST\UpdateStatement | |
||
833 | * \Doctrine\ORM\Query\AST\DeleteStatement |
||
834 | */ |
||
835 | 757 | public function QueryLanguage() |
|
864 | |||
865 | /** |
||
866 | * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] |
||
867 | * |
||
868 | * @return \Doctrine\ORM\Query\AST\SelectStatement |
||
869 | */ |
||
870 | 692 | public function SelectStatement() |
|
881 | |||
882 | /** |
||
883 | * UpdateStatement ::= UpdateClause [WhereClause] |
||
884 | * |
||
885 | * @return \Doctrine\ORM\Query\AST\UpdateStatement |
||
886 | */ |
||
887 | 31 | public function UpdateStatement() |
|
895 | |||
896 | /** |
||
897 | * DeleteStatement ::= DeleteClause [WhereClause] |
||
898 | * |
||
899 | * @return \Doctrine\ORM\Query\AST\DeleteStatement |
||
900 | */ |
||
901 | 40 | public function DeleteStatement() |
|
909 | |||
910 | /** |
||
911 | * IdentificationVariable ::= identifier |
||
912 | * |
||
913 | * @return string |
||
914 | */ |
||
915 | 734 | public function IdentificationVariable() |
|
929 | |||
930 | /** |
||
931 | * AliasIdentificationVariable = identifier |
||
932 | * |
||
933 | * @return string |
||
934 | */ |
||
935 | 717 | public function AliasIdentificationVariable() |
|
948 | |||
949 | /** |
||
950 | * AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier |
||
951 | * |
||
952 | * @return string |
||
953 | */ |
||
954 | 738 | public function AbstractSchemaName() |
|
974 | |||
975 | /** |
||
976 | * Validates an AbstractSchemaName, making sure the class exists. |
||
977 | * |
||
978 | * @param string $schemaName The name to validate. |
||
979 | * |
||
980 | * @throws QueryException if the name does not exist. |
||
981 | */ |
||
982 | 732 | private function validateAbstractSchemaName($schemaName) |
|
988 | |||
989 | /** |
||
990 | * AliasResultVariable ::= identifier |
||
991 | * |
||
992 | * @return string |
||
993 | */ |
||
994 | 68 | public function AliasResultVariable() |
|
995 | { |
||
996 | 68 | $this->match(Lexer::T_IDENTIFIER); |
|
997 | |||
998 | 64 | $resultVariable = $this->lexer->token['value']; |
|
999 | 64 | $exists = isset($this->queryComponents[$resultVariable]); |
|
1000 | |||
1001 | 64 | if ($exists) { |
|
1002 | $this->semanticalError("'$resultVariable' is already defined.", $this->lexer->token); |
||
1003 | } |
||
1004 | |||
1005 | 64 | return $resultVariable; |
|
1006 | } |
||
1007 | |||
1008 | /** |
||
1009 | * ResultVariable ::= identifier |
||
1010 | * |
||
1011 | * @return string |
||
1012 | */ |
||
1013 | 9 | public function ResultVariable() |
|
1028 | |||
1029 | /** |
||
1030 | * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) |
||
1031 | * |
||
1032 | * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression |
||
1033 | */ |
||
1034 | 228 | public function JoinAssociationPathExpression() |
|
1059 | |||
1060 | /** |
||
1061 | * Parses an arbitrary path expression and defers semantical validation |
||
1062 | * based on expected types. |
||
1063 | * |
||
1064 | * PathExpression ::= IdentificationVariable {"." identifier}* |
||
1065 | * |
||
1066 | * @param integer $expectedTypes |
||
1067 | * |
||
1068 | * @return \Doctrine\ORM\Query\AST\PathExpression |
||
1069 | */ |
||
1070 | 529 | public function PathExpression($expectedTypes) |
|
1100 | |||
1101 | /** |
||
1102 | * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression |
||
1103 | * |
||
1104 | * @return \Doctrine\ORM\Query\AST\PathExpression |
||
1105 | */ |
||
1106 | public function AssociationPathExpression() |
||
1113 | |||
1114 | /** |
||
1115 | * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression |
||
1116 | * |
||
1117 | * @return \Doctrine\ORM\Query\AST\PathExpression |
||
1118 | */ |
||
1119 | 441 | public function SingleValuedPathExpression() |
|
1126 | |||
1127 | /** |
||
1128 | * StateFieldPathExpression ::= IdentificationVariable "." StateField |
||
1129 | * |
||
1130 | * @return \Doctrine\ORM\Query\AST\PathExpression |
||
1131 | */ |
||
1132 | 177 | public function StateFieldPathExpression() |
|
1136 | |||
1137 | /** |
||
1138 | * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField |
||
1139 | * |
||
1140 | * @return \Doctrine\ORM\Query\AST\PathExpression |
||
1141 | */ |
||
1142 | 9 | public function SingleValuedAssociationPathExpression() |
|
1146 | |||
1147 | /** |
||
1148 | * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField |
||
1149 | * |
||
1150 | * @return \Doctrine\ORM\Query\AST\PathExpression |
||
1151 | */ |
||
1152 | 21 | public function CollectionValuedPathExpression() |
|
1156 | |||
1157 | /** |
||
1158 | * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} |
||
1159 | * |
||
1160 | * @return \Doctrine\ORM\Query\AST\SelectClause |
||
1161 | */ |
||
1162 | 692 | public function SelectClause() |
|
1186 | |||
1187 | /** |
||
1188 | * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression |
||
1189 | * |
||
1190 | * @return \Doctrine\ORM\Query\AST\SimpleSelectClause |
||
1191 | */ |
||
1192 | 27 | public function SimpleSelectClause() |
|
1205 | |||
1206 | /** |
||
1207 | * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}* |
||
1208 | * |
||
1209 | * @return \Doctrine\ORM\Query\AST\UpdateClause |
||
1210 | */ |
||
1211 | 31 | public function UpdateClause() |
|
1256 | |||
1257 | /** |
||
1258 | * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable |
||
1259 | * |
||
1260 | * @return \Doctrine\ORM\Query\AST\DeleteClause |
||
1261 | */ |
||
1262 | 40 | public function DeleteClause() |
|
1263 | { |
||
1264 | 40 | $this->match(Lexer::T_DELETE); |
|
1265 | |||
1266 | 40 | if ($this->lexer->isNextToken(Lexer::T_FROM)) { |
|
1267 | 8 | $this->match(Lexer::T_FROM); |
|
1268 | } |
||
1269 | |||
1270 | 40 | $token = $this->lexer->lookahead; |
|
1271 | 40 | $abstractSchemaName = $this->AbstractSchemaName(); |
|
1272 | |||
1273 | 40 | $this->validateAbstractSchemaName($abstractSchemaName); |
|
1274 | |||
1275 | 40 | $deleteClause = new AST\DeleteClause($abstractSchemaName); |
|
1276 | |||
1277 | 40 | if ($this->lexer->isNextToken(Lexer::T_AS)) { |
|
1278 | $this->match(Lexer::T_AS); |
||
1279 | } |
||
1280 | |||
1281 | 40 | $aliasIdentificationVariable = $this->AliasIdentificationVariable(); |
|
1282 | |||
1283 | 39 | $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable; |
|
1284 | 39 | $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName); |
|
1285 | |||
1286 | // Building queryComponent |
||
1287 | $queryComponent = [ |
||
1288 | 39 | 'metadata' => $class, |
|
1289 | 'parent' => null, |
||
1290 | 'relation' => null, |
||
1291 | 'map' => null, |
||
1292 | 39 | 'nestingLevel' => $this->nestingLevel, |
|
1293 | 39 | 'token' => $token, |
|
1294 | ]; |
||
1295 | |||
1296 | 39 | $this->queryComponents[$aliasIdentificationVariable] = $queryComponent; |
|
1297 | |||
1298 | 39 | return $deleteClause; |
|
1299 | } |
||
1300 | |||
1301 | /** |
||
1302 | * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* |
||
1303 | * |
||
1304 | * @return \Doctrine\ORM\Query\AST\FromClause |
||
1305 | */ |
||
1306 | 674 | public function FromClause() |
|
1321 | |||
1322 | /** |
||
1323 | * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* |
||
1324 | * |
||
1325 | * @return \Doctrine\ORM\Query\AST\SubselectFromClause |
||
1326 | */ |
||
1327 | 26 | public function SubselectFromClause() |
|
1342 | |||
1343 | /** |
||
1344 | * WhereClause ::= "WHERE" ConditionalExpression |
||
1345 | * |
||
1346 | * @return \Doctrine\ORM\Query\AST\WhereClause |
||
1347 | */ |
||
1348 | 313 | public function WhereClause() |
|
1354 | |||
1355 | /** |
||
1356 | * HavingClause ::= "HAVING" ConditionalExpression |
||
1357 | * |
||
1358 | * @return \Doctrine\ORM\Query\AST\HavingClause |
||
1359 | */ |
||
1360 | 4 | public function HavingClause() |
|
1366 | |||
1367 | /** |
||
1368 | * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* |
||
1369 | * |
||
1370 | * @return \Doctrine\ORM\Query\AST\GroupByClause |
||
1371 | */ |
||
1372 | 7 | public function GroupByClause() |
|
1387 | |||
1388 | /** |
||
1389 | * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* |
||
1390 | * |
||
1391 | * @return \Doctrine\ORM\Query\AST\OrderByClause |
||
1392 | */ |
||
1393 | 156 | public function OrderByClause() |
|
1409 | |||
1410 | /** |
||
1411 | * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] |
||
1412 | * |
||
1413 | * @return \Doctrine\ORM\Query\AST\Subselect |
||
1414 | */ |
||
1415 | 27 | public function Subselect() |
|
1432 | |||
1433 | /** |
||
1434 | * UpdateItem ::= SingleValuedPathExpression "=" NewValue |
||
1435 | * |
||
1436 | * @return \Doctrine\ORM\Query\AST\UpdateItem |
||
1437 | */ |
||
1438 | 31 | public function UpdateItem() |
|
1448 | |||
1449 | /** |
||
1450 | * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression |
||
1451 | * |
||
1452 | * @return string | \Doctrine\ORM\Query\AST\PathExpression |
||
1453 | */ |
||
1454 | 7 | public function GroupByItem() |
|
1455 | { |
||
1456 | // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression |
||
1457 | 7 | $glimpse = $this->lexer->glimpse(); |
|
1458 | |||
1459 | 7 | if ($glimpse['type'] === Lexer::T_DOT) { |
|
1460 | 1 | return $this->SingleValuedPathExpression(); |
|
1461 | } |
||
1462 | |||
1463 | // Still need to decide between IdentificationVariable or ResultVariable |
||
1464 | 6 | $lookaheadValue = $this->lexer->lookahead['value']; |
|
1465 | |||
1466 | 6 | if ( ! isset($this->queryComponents[$lookaheadValue])) { |
|
1467 | $this->semanticalError('Cannot group by undefined identification or result variable.'); |
||
1468 | } |
||
1469 | |||
1470 | 6 | return (isset($this->queryComponents[$lookaheadValue]['metadata'])) |
|
1471 | 4 | ? $this->IdentificationVariable() |
|
1472 | 6 | : $this->ResultVariable(); |
|
1473 | } |
||
1474 | |||
1475 | /** |
||
1476 | * OrderByItem ::= ( |
||
1477 | * SimpleArithmeticExpression | SingleValuedPathExpression | |
||
1478 | * ScalarExpression | ResultVariable | FunctionDeclaration |
||
1479 | * ) ["ASC" | "DESC"] |
||
1480 | * |
||
1481 | * @return \Doctrine\ORM\Query\AST\OrderByItem |
||
1482 | */ |
||
1483 | 156 | public function OrderByItem() |
|
1537 | |||
1538 | /** |
||
1539 | * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary | |
||
1540 | * EnumPrimary | SimpleEntityExpression | "NULL" |
||
1541 | * |
||
1542 | * NOTE: Since it is not possible to correctly recognize individual types, here is the full |
||
1543 | * grammar that needs to be supported: |
||
1544 | * |
||
1545 | * NewValue ::= SimpleArithmeticExpression | "NULL" |
||
1546 | * |
||
1547 | * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression |
||
1548 | * |
||
1549 | * @return AST\ArithmeticExpression |
||
1550 | */ |
||
1551 | 31 | public function NewValue() |
|
1567 | |||
1568 | /** |
||
1569 | * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}* |
||
1570 | * |
||
1571 | * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration |
||
1572 | */ |
||
1573 | 670 | public function IdentificationVariableDeclaration() |
|
1595 | |||
1596 | /** |
||
1597 | * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration |
||
1598 | * |
||
1599 | * {Internal note: WARNING: Solution is harder than a bare implementation. |
||
1600 | * Desired EBNF support: |
||
1601 | * |
||
1602 | * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) |
||
1603 | * |
||
1604 | * It demands that entire SQL generation to become programmatical. This is |
||
1605 | * needed because association based subselect requires "WHERE" conditional |
||
1606 | * expressions to be injected, but there is no scope to do that. Only scope |
||
1607 | * accessible is "FROM", prohibiting an easy implementation without larger |
||
1608 | * changes.} |
||
1609 | * |
||
1610 | * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration | |
||
1611 | * \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration |
||
1612 | */ |
||
1613 | 26 | public function SubselectIdentificationVariableDeclaration() |
|
1654 | |||
1655 | /** |
||
1656 | * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" |
||
1657 | * (JoinAssociationDeclaration | RangeVariableDeclaration) |
||
1658 | * ["WITH" ConditionalExpression] |
||
1659 | * |
||
1660 | * @return \Doctrine\ORM\Query\AST\Join |
||
1661 | */ |
||
1662 | 247 | public function Join() |
|
1710 | |||
1711 | /** |
||
1712 | * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable |
||
1713 | * |
||
1714 | * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration |
||
1715 | */ |
||
1716 | 670 | public function RangeVariableDeclaration() |
|
1744 | |||
1745 | /** |
||
1746 | * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy] |
||
1747 | * |
||
1748 | * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression |
||
1749 | */ |
||
1750 | 228 | public function JoinAssociationDeclaration() |
|
1781 | |||
1782 | /** |
||
1783 | * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet |
||
1784 | * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" |
||
1785 | * |
||
1786 | * @return \Doctrine\ORM\Query\AST\PartialObjectExpression |
||
1787 | */ |
||
1788 | 11 | public function PartialObjectExpression() |
|
1839 | |||
1840 | /** |
||
1841 | * NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")" |
||
1842 | * |
||
1843 | * @return \Doctrine\ORM\Query\AST\NewObjectExpression |
||
1844 | */ |
||
1845 | 26 | public function NewObjectExpression() |
|
1875 | |||
1876 | /** |
||
1877 | * NewObjectArg ::= ScalarExpression | "(" Subselect ")" |
||
1878 | * |
||
1879 | * @return mixed |
||
1880 | */ |
||
1881 | 26 | public function NewObjectArg() |
|
1896 | |||
1897 | /** |
||
1898 | * IndexBy ::= "INDEX" "BY" StateFieldPathExpression |
||
1899 | * |
||
1900 | * @return \Doctrine\ORM\Query\AST\IndexBy |
||
1901 | */ |
||
1902 | 11 | public function IndexBy() |
|
1913 | |||
1914 | /** |
||
1915 | * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | |
||
1916 | * StateFieldPathExpression | BooleanPrimary | CaseExpression | |
||
1917 | * InstanceOfExpression |
||
1918 | * |
||
1919 | * @return mixed One of the possible expressions or subexpressions. |
||
1920 | */ |
||
1921 | 129 | public function ScalarExpression() |
|
1922 | { |
||
1923 | 129 | $lookahead = $this->lexer->lookahead['type']; |
|
1924 | 129 | $peek = $this->lexer->glimpse(); |
|
1925 | |||
1926 | switch (true) { |
||
1927 | 129 | case ($lookahead === Lexer::T_INTEGER): |
|
1928 | 126 | case ($lookahead === Lexer::T_FLOAT): |
|
1929 | // SimpleArithmeticExpression : (- u.value ) or ( + u.value ) or ( - 1 ) or ( + 1 ) |
||
1930 | 126 | case ($lookahead === Lexer::T_MINUS): |
|
1931 | 126 | case ($lookahead === Lexer::T_PLUS): |
|
1932 | 15 | return $this->SimpleArithmeticExpression(); |
|
1933 | |||
1934 | 126 | case ($lookahead === Lexer::T_STRING): |
|
1935 | 13 | return $this->StringPrimary(); |
|
1936 | |||
1937 | 124 | case ($lookahead === Lexer::T_TRUE): |
|
1938 | 124 | case ($lookahead === Lexer::T_FALSE): |
|
1939 | 3 | $this->match($lookahead); |
|
1940 | |||
1941 | 3 | return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']); |
|
1942 | |||
1943 | 124 | case ($lookahead === Lexer::T_INPUT_PARAMETER): |
|
1944 | switch (true) { |
||
1945 | case $this->isMathOperator($peek): |
||
1946 | // :param + u.value |
||
1947 | return $this->SimpleArithmeticExpression(); |
||
1948 | default: |
||
1949 | return $this->InputParameter(); |
||
1950 | } |
||
1951 | |||
1952 | 124 | case ($lookahead === Lexer::T_CASE): |
|
1953 | 120 | case ($lookahead === Lexer::T_COALESCE): |
|
1954 | 120 | case ($lookahead === Lexer::T_NULLIF): |
|
1955 | // Since NULLIF and COALESCE can be identified as a function, |
||
1956 | // we need to check these before checking for FunctionDeclaration |
||
1957 | 8 | return $this->CaseExpression(); |
|
1958 | |||
1959 | 120 | case ($lookahead === Lexer::T_OPEN_PARENTHESIS): |
|
1960 | 3 | return $this->SimpleArithmeticExpression(); |
|
1961 | |||
1962 | // this check must be done before checking for a filed path expression |
||
1963 | 117 | case ($this->isFunction()): |
|
1964 | 4 | $this->lexer->peek(); // "(" |
|
1965 | |||
1966 | switch (true) { |
||
1967 | 4 | case ($this->isMathOperator($this->peekBeyondClosingParenthesis())): |
|
1968 | // SUM(u.id) + COUNT(u.id) |
||
1969 | return $this->SimpleArithmeticExpression(); |
||
1970 | |||
1971 | default: |
||
1972 | // IDENTITY(u) |
||
1973 | 4 | return $this->FunctionDeclaration(); |
|
1974 | } |
||
1975 | |||
1976 | break; |
||
1977 | // it is no function, so it must be a field path |
||
1978 | 116 | case ($lookahead === Lexer::T_IDENTIFIER): |
|
1979 | 116 | $this->lexer->peek(); // lookahead => '.' |
|
1980 | 116 | $this->lexer->peek(); // lookahead => token after '.' |
|
1981 | 116 | $peek = $this->lexer->peek(); // lookahead => token after the token after the '.' |
|
1982 | 116 | $this->lexer->resetPeek(); |
|
1983 | |||
1984 | 116 | if ($this->isMathOperator($peek)) { |
|
1985 | 6 | return $this->SimpleArithmeticExpression(); |
|
1986 | } |
||
1987 | |||
1988 | 112 | return $this->StateFieldPathExpression(); |
|
1989 | |||
1990 | default: |
||
1991 | $this->syntaxError(); |
||
1992 | } |
||
1993 | } |
||
1994 | |||
1995 | /** |
||
1996 | * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression |
||
1997 | * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" |
||
1998 | * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression |
||
1999 | * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" |
||
2000 | * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator |
||
2001 | * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression |
||
2002 | * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" |
||
2003 | * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" |
||
2004 | * |
||
2005 | * @return mixed One of the possible expressions or subexpressions. |
||
2006 | */ |
||
2007 | 18 | public function CaseExpression() |
|
2035 | |||
2036 | /** |
||
2037 | * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" |
||
2038 | * |
||
2039 | * @return \Doctrine\ORM\Query\AST\CoalesceExpression |
||
2040 | */ |
||
2041 | 3 | public function CoalesceExpression() |
|
2060 | |||
2061 | /** |
||
2062 | * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" |
||
2063 | * |
||
2064 | * @return \Doctrine\ORM\Query\AST\NullIfExpression |
||
2065 | */ |
||
2066 | 5 | public function NullIfExpression() |
|
2079 | |||
2080 | /** |
||
2081 | * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" |
||
2082 | * |
||
2083 | * @return \Doctrine\ORM\Query\AST\GeneralCaseExpression |
||
2084 | */ |
||
2085 | 8 | public function GeneralCaseExpression() |
|
2102 | |||
2103 | /** |
||
2104 | * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" |
||
2105 | * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator |
||
2106 | * |
||
2107 | * @return AST\SimpleCaseExpression |
||
2108 | */ |
||
2109 | 5 | public function SimpleCaseExpression() |
|
2127 | |||
2128 | /** |
||
2129 | * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression |
||
2130 | * |
||
2131 | * @return \Doctrine\ORM\Query\AST\WhenClause |
||
2132 | */ |
||
2133 | 8 | public function WhenClause() |
|
2141 | |||
2142 | /** |
||
2143 | * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression |
||
2144 | * |
||
2145 | * @return \Doctrine\ORM\Query\AST\SimpleWhenClause |
||
2146 | */ |
||
2147 | 5 | public function SimpleWhenClause() |
|
2155 | |||
2156 | /** |
||
2157 | * SelectExpression ::= ( |
||
2158 | * IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | |
||
2159 | * PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression |
||
2160 | * ) [["AS"] ["HIDDEN"] AliasResultVariable] |
||
2161 | * |
||
2162 | * @return \Doctrine\ORM\Query\AST\SelectExpression |
||
2163 | */ |
||
2164 | 692 | public function SelectExpression() |
|
2165 | { |
||
2166 | 692 | $expression = null; |
|
2167 | 692 | $identVariable = null; |
|
2168 | 692 | $peek = $this->lexer->glimpse(); |
|
2169 | 692 | $lookaheadType = $this->lexer->lookahead['type']; |
|
2170 | |||
2171 | switch (true) { |
||
2172 | // ScalarExpression (u.name) |
||
2173 | 692 | case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT): |
|
2174 | 91 | $expression = $this->ScalarExpression(); |
|
2175 | 91 | break; |
|
2176 | |||
2177 | // IdentificationVariable (u) |
||
2178 | 634 | case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS): |
|
2179 | 557 | $expression = $identVariable = $this->IdentificationVariable(); |
|
2180 | 557 | break; |
|
2181 | |||
2182 | // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...)) |
||
2183 | 111 | case ($lookaheadType === Lexer::T_CASE): |
|
2184 | 106 | case ($lookaheadType === Lexer::T_COALESCE): |
|
2185 | 104 | case ($lookaheadType === Lexer::T_NULLIF): |
|
2186 | 9 | $expression = $this->CaseExpression(); |
|
2187 | 9 | break; |
|
2188 | |||
2189 | // DQL Function (SUM(u.value) or SUM(u.value) + 1) |
||
2190 | 102 | case ($this->isFunction()): |
|
2191 | 42 | $this->lexer->peek(); // "(" |
|
2192 | |||
2193 | switch (true) { |
||
2194 | 42 | case ($this->isMathOperator($this->peekBeyondClosingParenthesis())): |
|
2195 | // SUM(u.id) + COUNT(u.id) |
||
2196 | $expression = $this->ScalarExpression(); |
||
2197 | break; |
||
2198 | |||
2199 | default: |
||
2200 | // IDENTITY(u) |
||
2201 | 42 | $expression = $this->FunctionDeclaration(); |
|
2202 | 31 | break; |
|
2203 | } |
||
2204 | |||
2205 | 31 | break; |
|
2206 | |||
2207 | // PartialObjectExpression (PARTIAL u.{id, name}) |
||
2208 | 60 | case ($lookaheadType === Lexer::T_PARTIAL): |
|
2209 | 11 | $expression = $this->PartialObjectExpression(); |
|
2210 | 11 | $identVariable = $expression->identificationVariable; |
|
2211 | 11 | break; |
|
2212 | |||
2213 | // Subselect |
||
2214 | 49 | case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT): |
|
2215 | 5 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
|
2216 | 5 | $expression = $this->Subselect(); |
|
2217 | 5 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
|
2218 | 5 | break; |
|
2219 | |||
2220 | // Shortcut: ScalarExpression => SimpleArithmeticExpression |
||
2221 | 44 | case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS): |
|
2222 | 41 | case ($lookaheadType === Lexer::T_INTEGER): |
|
2223 | 39 | case ($lookaheadType === Lexer::T_STRING): |
|
2224 | 30 | case ($lookaheadType === Lexer::T_FLOAT): |
|
2225 | // SimpleArithmeticExpression : (- u.value ) or ( + u.value ) |
||
2226 | 30 | case ($lookaheadType === Lexer::T_MINUS): |
|
2227 | 30 | case ($lookaheadType === Lexer::T_PLUS): |
|
2228 | 15 | $expression = $this->SimpleArithmeticExpression(); |
|
2229 | 15 | break; |
|
2230 | |||
2231 | // NewObjectExpression (New ClassName(id, name)) |
||
2232 | 29 | case ($lookaheadType === Lexer::T_NEW): |
|
2233 | 26 | $expression = $this->NewObjectExpression(); |
|
2234 | 26 | break; |
|
2235 | |||
2236 | default: |
||
2237 | 3 | $this->syntaxError( |
|
2238 | 3 | 'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression', |
|
2239 | 3 | $this->lexer->lookahead |
|
2240 | ); |
||
2241 | } |
||
2242 | |||
2243 | // [["AS"] ["HIDDEN"] AliasResultVariable] |
||
2244 | 685 | $mustHaveAliasResultVariable = false; |
|
2245 | |||
2246 | 685 | if ($this->lexer->isNextToken(Lexer::T_AS)) { |
|
2247 | 66 | $this->match(Lexer::T_AS); |
|
2248 | |||
2249 | 66 | $mustHaveAliasResultVariable = true; |
|
2250 | } |
||
2251 | |||
2252 | 685 | $hiddenAliasResultVariable = false; |
|
2253 | |||
2254 | 685 | if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) { |
|
2255 | 2 | $this->match(Lexer::T_HIDDEN); |
|
2256 | |||
2257 | 2 | $hiddenAliasResultVariable = true; |
|
2258 | } |
||
2259 | |||
2260 | 685 | $aliasResultVariable = null; |
|
2261 | |||
2262 | 685 | if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) { |
|
2263 | 68 | $token = $this->lexer->lookahead; |
|
2264 | 68 | $aliasResultVariable = $this->AliasResultVariable(); |
|
2265 | |||
2266 | // Include AliasResultVariable in query components. |
||
2267 | 64 | $this->queryComponents[$aliasResultVariable] = [ |
|
2268 | 64 | 'resultVariable' => $expression, |
|
2269 | 64 | 'nestingLevel' => $this->nestingLevel, |
|
2270 | 64 | 'token' => $token, |
|
2271 | ]; |
||
2272 | } |
||
2273 | |||
2274 | // AST |
||
2275 | |||
2276 | 681 | $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable); |
|
2277 | |||
2278 | 681 | if ($identVariable) { |
|
2279 | 565 | $this->identVariableExpressions[$identVariable] = $expr; |
|
2280 | } |
||
2281 | |||
2282 | 681 | return $expr; |
|
2283 | } |
||
2284 | |||
2285 | /** |
||
2286 | * SimpleSelectExpression ::= ( |
||
2287 | * StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | |
||
2288 | * AggregateExpression | "(" Subselect ")" | ScalarExpression |
||
2289 | * ) [["AS"] AliasResultVariable] |
||
2290 | * |
||
2291 | * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression |
||
2292 | */ |
||
2293 | 27 | public function SimpleSelectExpression() |
|
2294 | { |
||
2295 | 27 | $peek = $this->lexer->glimpse(); |
|
2296 | |||
2297 | 27 | switch ($this->lexer->lookahead['type']) { |
|
2298 | 27 | case Lexer::T_IDENTIFIER: |
|
2299 | switch (true) { |
||
2300 | 19 | case ($peek['type'] === Lexer::T_DOT): |
|
2301 | 16 | $expression = $this->StateFieldPathExpression(); |
|
2302 | |||
2303 | 16 | return new AST\SimpleSelectExpression($expression); |
|
2304 | |||
2305 | 3 | case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS): |
|
2306 | 2 | $expression = $this->IdentificationVariable(); |
|
2307 | |||
2308 | 2 | return new AST\SimpleSelectExpression($expression); |
|
2309 | |||
2310 | 1 | case ($this->isFunction()): |
|
2311 | // SUM(u.id) + COUNT(u.id) |
||
2312 | 1 | if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) { |
|
2313 | return new AST\SimpleSelectExpression($this->ScalarExpression()); |
||
2314 | } |
||
2315 | // COUNT(u.id) |
||
2316 | 1 | if ($this->isAggregateFunction($this->lexer->lookahead['type'])) { |
|
2317 | return new AST\SimpleSelectExpression($this->AggregateExpression()); |
||
2318 | } |
||
2319 | // IDENTITY(u) |
||
2320 | 1 | return new AST\SimpleSelectExpression($this->FunctionDeclaration()); |
|
2321 | |||
2322 | default: |
||
2323 | // Do nothing |
||
2324 | } |
||
2325 | break; |
||
2326 | |||
2327 | 8 | case Lexer::T_OPEN_PARENTHESIS: |
|
2328 | if ($peek['type'] !== Lexer::T_SELECT) { |
||
2329 | // Shortcut: ScalarExpression => SimpleArithmeticExpression |
||
2330 | $expression = $this->SimpleArithmeticExpression(); |
||
2331 | |||
2332 | return new AST\SimpleSelectExpression($expression); |
||
2333 | } |
||
2334 | |||
2335 | // Subselect |
||
2336 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
||
2337 | $expression = $this->Subselect(); |
||
2338 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
||
2339 | |||
2340 | return new AST\SimpleSelectExpression($expression); |
||
2341 | |||
2342 | default: |
||
2343 | // Do nothing |
||
2344 | } |
||
2345 | |||
2346 | 8 | $this->lexer->peek(); |
|
2347 | |||
2348 | 8 | $expression = $this->ScalarExpression(); |
|
2349 | 7 | $expr = new AST\SimpleSelectExpression($expression); |
|
2350 | |||
2351 | 7 | if ($this->lexer->isNextToken(Lexer::T_AS)) { |
|
2352 | $this->match(Lexer::T_AS); |
||
2353 | } |
||
2354 | |||
2355 | 7 | if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) { |
|
2356 | $token = $this->lexer->lookahead; |
||
2357 | $resultVariable = $this->AliasResultVariable(); |
||
2358 | $expr->fieldIdentificationVariable = $resultVariable; |
||
2359 | |||
2360 | // Include AliasResultVariable in query components. |
||
2361 | $this->queryComponents[$resultVariable] = [ |
||
2362 | 'resultvariable' => $expr, |
||
2363 | 'nestingLevel' => $this->nestingLevel, |
||
2364 | 'token' => $token, |
||
2365 | ]; |
||
2366 | } |
||
2367 | |||
2368 | 7 | return $expr; |
|
2369 | } |
||
2370 | |||
2371 | /** |
||
2372 | * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* |
||
2373 | * |
||
2374 | * @return \Doctrine\ORM\Query\AST\ConditionalExpression |
||
2375 | */ |
||
2376 | 337 | public function ConditionalExpression() |
|
2395 | |||
2396 | /** |
||
2397 | * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* |
||
2398 | * |
||
2399 | * @return \Doctrine\ORM\Query\AST\ConditionalTerm |
||
2400 | */ |
||
2401 | 337 | public function ConditionalTerm() |
|
2420 | |||
2421 | /** |
||
2422 | * ConditionalFactor ::= ["NOT"] ConditionalPrimary |
||
2423 | * |
||
2424 | * @return \Doctrine\ORM\Query\AST\ConditionalFactor |
||
2425 | */ |
||
2426 | 337 | public function ConditionalFactor() |
|
2449 | |||
2450 | /** |
||
2451 | * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" |
||
2452 | * |
||
2453 | * @return \Doctrine\ORM\Query\AST\ConditionalPrimary |
||
2454 | */ |
||
2455 | 337 | public function ConditionalPrimary() |
|
2482 | |||
2483 | /** |
||
2484 | * SimpleConditionalExpression ::= |
||
2485 | * ComparisonExpression | BetweenExpression | LikeExpression | |
||
2486 | * InExpression | NullComparisonExpression | ExistsExpression | |
||
2487 | * EmptyCollectionComparisonExpression | CollectionMemberExpression | |
||
2488 | * InstanceOfExpression |
||
2489 | */ |
||
2490 | 337 | public function SimpleConditionalExpression() |
|
2579 | |||
2580 | /** |
||
2581 | * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" |
||
2582 | * |
||
2583 | * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression |
||
2584 | */ |
||
2585 | 4 | public function EmptyCollectionComparisonExpression() |
|
2601 | |||
2602 | /** |
||
2603 | * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression |
||
2604 | * |
||
2605 | * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression |
||
2606 | * SimpleEntityExpression ::= IdentificationVariable | InputParameter |
||
2607 | * |
||
2608 | * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression |
||
2609 | */ |
||
2610 | 7 | public function CollectionMemberExpression() |
|
2634 | |||
2635 | /** |
||
2636 | * Literal ::= string | char | integer | float | boolean |
||
2637 | * |
||
2638 | * @return \Doctrine\ORM\Query\AST\Literal |
||
2639 | */ |
||
2640 | 146 | public function Literal() |
|
2665 | |||
2666 | /** |
||
2667 | * InParameter ::= Literal | InputParameter |
||
2668 | * |
||
2669 | * @return string | \Doctrine\ORM\Query\AST\InputParameter |
||
2670 | */ |
||
2671 | 25 | public function InParameter() |
|
2679 | |||
2680 | /** |
||
2681 | * InputParameter ::= PositionalParameter | NamedParameter |
||
2682 | * |
||
2683 | * @return \Doctrine\ORM\Query\AST\InputParameter |
||
2684 | */ |
||
2685 | 161 | public function InputParameter() |
|
2691 | |||
2692 | /** |
||
2693 | * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" |
||
2694 | * |
||
2695 | * @return \Doctrine\ORM\Query\AST\ArithmeticExpression |
||
2696 | */ |
||
2697 | 294 | public function ArithmeticExpression() |
|
2717 | |||
2718 | /** |
||
2719 | * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* |
||
2720 | * |
||
2721 | * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression |
||
2722 | */ |
||
2723 | 364 | public function SimpleArithmeticExpression() |
|
2743 | |||
2744 | /** |
||
2745 | * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* |
||
2746 | * |
||
2747 | * @return \Doctrine\ORM\Query\AST\ArithmeticTerm |
||
2748 | */ |
||
2749 | 364 | public function ArithmeticTerm() |
|
2769 | |||
2770 | /** |
||
2771 | * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary |
||
2772 | * |
||
2773 | * @return \Doctrine\ORM\Query\AST\ArithmeticFactor |
||
2774 | */ |
||
2775 | 364 | public function ArithmeticFactor() |
|
2794 | |||
2795 | /** |
||
2796 | * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression |
||
2797 | * | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings |
||
2798 | * | FunctionsReturningDatetime | IdentificationVariable | ResultVariable |
||
2799 | * | InputParameter | CaseExpression |
||
2800 | */ |
||
2801 | 368 | public function ArithmeticPrimary() |
|
2849 | |||
2850 | /** |
||
2851 | * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")" |
||
2852 | * |
||
2853 | * @return \Doctrine\ORM\Query\AST\Subselect | |
||
2854 | * string |
||
2855 | */ |
||
2856 | 13 | public function StringExpression() |
|
2877 | |||
2878 | /** |
||
2879 | * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression |
||
2880 | */ |
||
2881 | 49 | public function StringPrimary() |
|
2919 | |||
2920 | /** |
||
2921 | * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression |
||
2922 | * |
||
2923 | * @return \Doctrine\ORM\Query\AST\PathExpression | |
||
2924 | * \Doctrine\ORM\Query\AST\SimpleEntityExpression |
||
2925 | */ |
||
2926 | 7 | public function EntityExpression() |
|
2936 | |||
2937 | /** |
||
2938 | * SimpleEntityExpression ::= IdentificationVariable | InputParameter |
||
2939 | * |
||
2940 | * @return string | \Doctrine\ORM\Query\AST\InputParameter |
||
2941 | */ |
||
2942 | 6 | public function SimpleEntityExpression() |
|
2950 | |||
2951 | /** |
||
2952 | * AggregateExpression ::= |
||
2953 | * ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")" |
||
2954 | * |
||
2955 | * @return \Doctrine\ORM\Query\AST\AggregateExpression |
||
2956 | */ |
||
2957 | 15 | public function AggregateExpression() |
|
2958 | { |
||
2959 | 15 | $lookaheadType = $this->lexer->lookahead['type']; |
|
2960 | 15 | $isDistinct = false; |
|
2961 | |||
2962 | 15 | if ( ! in_array($lookaheadType, [Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM])) { |
|
2963 | $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT'); |
||
2964 | } |
||
2965 | |||
2966 | 15 | $this->match($lookaheadType); |
|
2967 | 15 | $functionName = $this->lexer->token['value']; |
|
2968 | 15 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
|
2969 | |||
2970 | 15 | if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) { |
|
2971 | $this->match(Lexer::T_DISTINCT); |
||
2972 | $isDistinct = true; |
||
2973 | } |
||
2974 | |||
2975 | 15 | $pathExp = $this->SimpleArithmeticExpression(); |
|
2976 | |||
2977 | 15 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
|
2978 | |||
2979 | 15 | return new AST\AggregateExpression($functionName, $pathExp, $isDistinct); |
|
2980 | } |
||
2981 | |||
2982 | /** |
||
2983 | * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" |
||
2984 | * |
||
2985 | * @return \Doctrine\ORM\Query\AST\QuantifiedExpression |
||
2986 | */ |
||
2987 | 3 | public function QuantifiedExpression() |
|
3006 | |||
3007 | /** |
||
3008 | * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression |
||
3009 | * |
||
3010 | * @return \Doctrine\ORM\Query\AST\BetweenExpression |
||
3011 | */ |
||
3012 | 8 | public function BetweenExpression() |
|
3032 | |||
3033 | /** |
||
3034 | * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) |
||
3035 | * |
||
3036 | * @return \Doctrine\ORM\Query\AST\ComparisonExpression |
||
3037 | */ |
||
3038 | 261 | public function ComparisonExpression() |
|
3050 | |||
3051 | /** |
||
3052 | * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" |
||
3053 | * |
||
3054 | * @return \Doctrine\ORM\Query\AST\InExpression |
||
3055 | */ |
||
3056 | 34 | public function InExpression() |
|
3086 | |||
3087 | /** |
||
3088 | * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")") |
||
3089 | * |
||
3090 | * @return \Doctrine\ORM\Query\AST\InstanceOfExpression |
||
3091 | */ |
||
3092 | 12 | public function InstanceOfExpression() |
|
3130 | |||
3131 | /** |
||
3132 | * InstanceOfParameter ::= AbstractSchemaName | InputParameter |
||
3133 | * |
||
3134 | * @return mixed |
||
3135 | */ |
||
3136 | 12 | public function InstanceOfParameter() |
|
3150 | |||
3151 | /** |
||
3152 | * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char] |
||
3153 | * |
||
3154 | * @return \Doctrine\ORM\Query\AST\LikeExpression |
||
3155 | */ |
||
3156 | 13 | public function LikeExpression() |
|
3189 | |||
3190 | /** |
||
3191 | * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL" |
||
3192 | * |
||
3193 | * @return \Doctrine\ORM\Query\AST\NullComparisonExpression |
||
3194 | */ |
||
3195 | 11 | public function NullComparisonExpression() |
|
3196 | { |
||
3197 | switch (true) { |
||
3198 | 11 | case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER): |
|
3199 | $this->match(Lexer::T_INPUT_PARAMETER); |
||
3200 | |||
3201 | $expr = new AST\InputParameter($this->lexer->token['value']); |
||
3202 | break; |
||
3203 | |||
3204 | 11 | case $this->lexer->isNextToken(Lexer::T_NULLIF): |
|
3205 | 1 | $expr = $this->NullIfExpression(); |
|
3206 | 1 | break; |
|
3207 | |||
3208 | 11 | case $this->lexer->isNextToken(Lexer::T_COALESCE): |
|
3209 | 1 | $expr = $this->CoalesceExpression(); |
|
3210 | 1 | break; |
|
3211 | |||
3212 | 11 | case $this->isFunction(): |
|
3213 | 2 | $expr = $this->FunctionDeclaration(); |
|
3214 | 1 | break; |
|
3215 | |||
3216 | default: |
||
3217 | // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression |
||
3218 | 10 | $glimpse = $this->lexer->glimpse(); |
|
3219 | |||
3220 | 10 | if ($glimpse['type'] === Lexer::T_DOT) { |
|
3221 | 9 | $expr = $this->SingleValuedPathExpression(); |
|
3222 | |||
3223 | // Leave switch statement |
||
3224 | 9 | break; |
|
3225 | } |
||
3226 | |||
3227 | 1 | $lookaheadValue = $this->lexer->lookahead['value']; |
|
3228 | |||
3229 | // Validate existing component |
||
3230 | 1 | if ( ! isset($this->queryComponents[$lookaheadValue])) { |
|
3231 | $this->semanticalError('Cannot add having condition on undefined result variable.'); |
||
3232 | } |
||
3233 | |||
3234 | // Validate SingleValuedPathExpression (ie.: "product") |
||
3235 | 1 | if (isset($this->queryComponents[$lookaheadValue]['metadata'])) { |
|
3236 | 1 | $expr = $this->SingleValuedPathExpression(); |
|
3237 | 1 | break; |
|
3238 | } |
||
3239 | |||
3240 | // Validating ResultVariable |
||
3241 | if ( ! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) { |
||
3242 | $this->semanticalError('Cannot add having condition on a non result variable.'); |
||
3243 | } |
||
3244 | |||
3245 | $expr = $this->ResultVariable(); |
||
3246 | break; |
||
3247 | } |
||
3248 | |||
3249 | 11 | $nullCompExpr = new AST\NullComparisonExpression($expr); |
|
3250 | |||
3251 | 11 | $this->match(Lexer::T_IS); |
|
3252 | |||
3253 | 11 | if ($this->lexer->isNextToken(Lexer::T_NOT)) { |
|
3254 | 3 | $this->match(Lexer::T_NOT); |
|
3255 | |||
3256 | 3 | $nullCompExpr->not = true; |
|
3257 | } |
||
3258 | |||
3259 | 11 | $this->match(Lexer::T_NULL); |
|
3260 | |||
3261 | 11 | return $nullCompExpr; |
|
3262 | } |
||
3263 | |||
3264 | /** |
||
3265 | * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" |
||
3266 | * |
||
3267 | * @return \Doctrine\ORM\Query\AST\ExistsExpression |
||
3268 | */ |
||
3269 | 7 | public function ExistsExpression() |
|
3288 | |||
3289 | /** |
||
3290 | * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" |
||
3291 | * |
||
3292 | * @return string |
||
3293 | */ |
||
3294 | 259 | public function ComparisonOperator() |
|
3337 | |||
3338 | /** |
||
3339 | * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime |
||
3340 | * |
||
3341 | * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode |
||
3342 | */ |
||
3343 | 71 | public function FunctionDeclaration() |
|
3368 | |||
3369 | /** |
||
3370 | * Helper function for FunctionDeclaration grammar rule. |
||
3371 | * |
||
3372 | * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode |
||
3373 | */ |
||
3374 | 71 | private function CustomFunctionDeclaration() |
|
3396 | |||
3397 | /** |
||
3398 | * FunctionsReturningNumerics ::= |
||
3399 | * "LENGTH" "(" StringPrimary ")" | |
||
3400 | * "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | |
||
3401 | * "ABS" "(" SimpleArithmeticExpression ")" | |
||
3402 | * "SQRT" "(" SimpleArithmeticExpression ")" | |
||
3403 | * "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | |
||
3404 | * "SIZE" "(" CollectionValuedPathExpression ")" | |
||
3405 | * "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | |
||
3406 | * "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | |
||
3407 | * "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
||
3408 | * |
||
3409 | * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode |
||
3410 | */ |
||
3411 | 37 | public function FunctionsReturningNumerics() |
|
3421 | |||
3422 | /** |
||
3423 | * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode |
||
3424 | */ |
||
3425 | 2 | public function CustomFunctionsReturningNumerics() |
|
3439 | |||
3440 | /** |
||
3441 | * FunctionsReturningDateTime ::= |
||
3442 | * "CURRENT_DATE" | |
||
3443 | * "CURRENT_TIME" | |
||
3444 | * "CURRENT_TIMESTAMP" | |
||
3445 | * "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" | |
||
3446 | * "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" |
||
3447 | * |
||
3448 | * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode |
||
3449 | */ |
||
3450 | 7 | public function FunctionsReturningDatetime() |
|
3460 | |||
3461 | /** |
||
3462 | * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode |
||
3463 | */ |
||
3464 | public function CustomFunctionsReturningDatetime() |
||
3478 | |||
3479 | /** |
||
3480 | * FunctionsReturningStrings ::= |
||
3481 | * "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary}* ")" | |
||
3482 | * "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | |
||
3483 | * "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | |
||
3484 | * "LOWER" "(" StringPrimary ")" | |
||
3485 | * "UPPER" "(" StringPrimary ")" | |
||
3486 | * "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")" |
||
3487 | * |
||
3488 | * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode |
||
3489 | */ |
||
3490 | 29 | public function FunctionsReturningStrings() |
|
3500 | |||
3501 | /** |
||
3502 | * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode |
||
3503 | */ |
||
3504 | 2 | public function CustomFunctionsReturningStrings() |
|
3518 | } |
||
3519 |
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..