fix(Query): keep all occurrences of a repeated identification variable
Parser::$identVariableExpressions was keyed by alias and held a single SelectExpression per alias, so listing the same identification variable twice in a SELECT clause made the second node overwrite the first. fixIdentificationVariableOrder() only unsets and re-appends the nodes it knows about, so the forgotten first node stayed at the head of the select clause, ahead of its own parent. The hydrator then reached the child before the parent existed in resultPointers, took the "parent object of relation not found" branch in ObjectHydrator and unset the 'fetched' hint for the association. Because the hints are only rebuilt in prepare(), the association stayed marked as not fetched for the rest of the hydration run: the fetch join was silently dropped and every parent's collection lazy loaded, while the query still returned correct data. Track every select expression per identification variable and move all of them when reordering, so duplicates are re-appended together behind their parent, in declaration order. With a single occurrence per alias the inner loop runs once and the behaviour is unchanged.
This commit is contained in:
1 parent
8703a8e5de
commit
2c75209ce3
2 files changed
+36
-7
No files matched your search
+15
-7
@@ -19,6 +19,7 @@ use ReflectionClass;
|
||||
|
||||
use function array_intersect;
|
||||
use function array_key_exists;
|
||||
use function array_keys;
|
||||
use function array_search;
|
||||
use function assert;
|
||||
use function class_exists;
|
||||
@@ -167,7 +168,13 @@ final class Parser
|
||||
*/
|
||||
private $customOutputWalker;
|
||||
|
||||
/** @phpstan-var array<string, AST\SelectExpression> */
|
||||
/**
|
||||
* All select expressions that refer to an identification variable, grouped
|
||||
* by that variable. An alias may be listed more than once in the select
|
||||
* clause, so every occurrence has to be tracked, not just the last one.
|
||||
*
|
||||
* @phpstan-var array<string, non-empty-list<AST\SelectExpression>>
|
||||
*/
|
||||
private array $identVariableExpressions = [];
|
||||
|
||||
/**
|
||||
@@ -405,12 +412,13 @@ final class Parser
|
||||
continue;
|
||||
}
|
||||
|
||||
$expr = $this->identVariableExpressions[$dqlAlias];
|
||||
$key = array_search($expr, $AST->selectClause->selectExpressions, true);
|
||||
foreach ($this->identVariableExpressions[$dqlAlias] as $expr) {
|
||||
$key = array_search($expr, $AST->selectClause->selectExpressions, true);
|
||||
|
||||
unset($AST->selectClause->selectExpressions[$key]);
|
||||
unset($AST->selectClause->selectExpressions[$key]);
|
||||
|
||||
$AST->selectClause->selectExpressions[] = $expr;
|
||||
$AST->selectClause->selectExpressions[] = $expr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -796,7 +804,7 @@ final class Parser
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
|
||||
foreach (array_keys($this->identVariableExpressions) as $dqlAlias) {
|
||||
if (isset($this->queryComponents[$dqlAlias]) && ! isset($this->queryComponents[$dqlAlias]['parent'])) {
|
||||
return;
|
||||
}
|
||||
@@ -2290,7 +2298,7 @@ final class Parser
|
||||
$expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
|
||||
|
||||
if ($identVariable) {
|
||||
$this->identVariableExpressions[$identVariable] = $expr;
|
||||
$this->identVariableExpressions[$identVariable][] = $expr;
|
||||
}
|
||||
|
||||
return $expr;
|
||||
|
||||
Reference in new issue
Block a user