diff --git a/src/Query/Parser.php b/src/Query/Parser.php index e666d1b..ca1f330 100644 --- a/src/Query/Parser.php +++ b/src/Query/Parser.php @@ -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 */ + /** + * 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> + */ 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; diff --git a/tests/Tests/ORM/Query/SelectSqlGenerationTest.php b/tests/Tests/ORM/Query/SelectSqlGenerationTest.php index 0678fc5..c379d3d 100644 --- a/tests/Tests/ORM/Query/SelectSqlGenerationTest.php +++ b/tests/Tests/ORM/Query/SelectSqlGenerationTest.php @@ -222,6 +222,27 @@ class SelectSqlGenerationTest extends OrmTestCase ); } + #[Group('GH12585')] + public function testRepeatedIdentificationVariableKeepsSelectClauseInDeclarationOrder(): void + { + // The repeated collection alias must not be hoisted in front of its own parent, + // otherwise the hydrator sees the child before the parent and stops fetch joining. + $this->assertSqlGeneration( + 'SELECT u, p, p FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.phonenumbers p', + 'SELECT c0_.id AS id_0, c0_.status AS status_1, c0_.username AS username_2, c0_.name AS name_3, c1_.phonenumber AS phonenumber_4, c1_.phonenumber AS phonenumber_5, c0_.email_id AS email_id_6, c1_.user_id AS user_id_7 FROM cms_users c0_ INNER JOIN cms_phonenumbers c1_ ON c0_.id = c1_.user_id', + ); + + $this->assertSqlGeneration( + 'SELECT u, p, u FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.phonenumbers p', + 'SELECT c0_.id AS id_0, c0_.status AS status_1, c0_.username AS username_2, c0_.name AS name_3, c0_.id AS id_4, c0_.status AS status_5, c0_.username AS username_6, c0_.name AS name_7, c1_.phonenumber AS phonenumber_8, c0_.email_id AS email_id_9, c1_.user_id AS user_id_10 FROM cms_users c0_ INNER JOIN cms_phonenumbers c1_ ON c0_.id = c1_.user_id', + ); + + $this->assertSqlGeneration( + 'SELECT u, p, a, p FROM Doctrine\Tests\Models\CMS\CmsUser u JOIN u.phonenumbers p JOIN u.articles a', + 'SELECT c0_.id AS id_0, c0_.status AS status_1, c0_.username AS username_2, c0_.name AS name_3, c1_.phonenumber AS phonenumber_4, c1_.phonenumber AS phonenumber_5, c2_.id AS id_6, c2_.topic AS topic_7, c2_.text AS text_8, c2_.version AS version_9, c0_.email_id AS email_id_10, c1_.user_id AS user_id_11, c2_.user_id AS user_id_12 FROM cms_users c0_ INNER JOIN cms_phonenumbers c1_ ON c0_.id = c1_.user_id INNER JOIN cms_articles c2_ ON c0_.id = c2_.user_id', + ); + } + public function testSupportsSelectWithSingleValuedAssociationJoin(): void { $this->assertSqlGeneration(