From 9a896dda86ea5948b44ee36db87f136d656b4bcf Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 4 Jun 2026 15:09:03 +0200 Subject: [PATCH] fix(doctrine): extract alias from sql function in orderby parts QueryChecker::hasOrderByOnFetchJoinedToManyAssociation exploded each orderBy part on "." to derive an alias. For SQL function calls such as "find_in_set(t.type, 'INTERVIEW')", the first segment was "find_in_set(t", which QueryBuilderHelper::traverseJoins then rejected with LogicException: The alias "find_in_set(t" does not exist. Strip any leading SQL function prefix before treating the segment as an alias. Fixes #8083 --- .../Orm/Tests/Util/QueryCheckerTest.php | 27 +++++++++++++++++++ src/Doctrine/Orm/Util/QueryChecker.php | 3 +++ 2 files changed, 30 insertions(+) diff --git a/src/Doctrine/Orm/Tests/Util/QueryCheckerTest.php b/src/Doctrine/Orm/Tests/Util/QueryCheckerTest.php index 37969167be0..0d1a09b2c77 100644 --- a/src/Doctrine/Orm/Tests/Util/QueryCheckerTest.php +++ b/src/Doctrine/Orm/Tests/Util/QueryCheckerTest.php @@ -204,6 +204,33 @@ public function testHasOrderByOnFetchJoinedToManyAssociationWithJoinByAssociatio $this->assertTrue(QueryChecker::hasOrderByOnFetchJoinedToManyAssociation($queryBuilder, $managerRegistryProphecy->reveal())); } + public function testHasOrderByOnFetchJoinedToManyAssociationWithSqlFunctionInOrderBy(): void + { + $dummyMetadata = new ClassMetadata(Dummy::class); + $dummyMetadata->mapManyToMany([ + 'fieldName' => 'relatedDummies', + 'targetEntity' => RelatedDummy::class, + ]); + + $relatedDummyMetadata = new ClassMetadata(RelatedDummy::class); + + $entityManagerProphecy = $this->prophesize(EntityManagerInterface::class); + $entityManagerProphecy->getClassMetadata(Dummy::class)->willReturn($dummyMetadata); + $entityManagerProphecy->getClassMetadata(RelatedDummy::class)->willReturn($relatedDummyMetadata); + + $queryBuilder = new QueryBuilder($entityManagerProphecy->reveal()); + $queryBuilder->select('d', 'a_1'); + $queryBuilder->from(Dummy::class, 'd'); + $queryBuilder->leftJoin('d.relatedDummies', 'a_1'); + $queryBuilder->addOrderBy("find_in_set(d.name, 'INTERVIEW')", 'ASC'); + + $managerRegistryProphecy = $this->prophesize(ManagerRegistry::class); + $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($entityManagerProphecy); + $managerRegistryProphecy->getManagerForClass(RelatedDummy::class)->willReturn($entityManagerProphecy); + + $this->assertFalse(QueryChecker::hasOrderByOnFetchJoinedToManyAssociation($queryBuilder, $managerRegistryProphecy->reveal())); + } + public function testHasJoinedToManyAssociationWithoutJoin(): void { $entityManagerProphecy = $this->prophesize(EntityManagerInterface::class); diff --git a/src/Doctrine/Orm/Util/QueryChecker.php b/src/Doctrine/Orm/Util/QueryChecker.php index fc1bad9244a..ff49a3d4e22 100644 --- a/src/Doctrine/Orm/Util/QueryChecker.php +++ b/src/Doctrine/Orm/Util/QueryChecker.php @@ -107,6 +107,9 @@ public static function hasOrderByOnFetchJoinedToManyAssociation(QueryBuilder $qu if (str_contains((string) $part, '.')) { [$alias] = explode('.', (string) $part); + // strip leading SQL function call, e.g. "find_in_set(t.type, ...)" → "t" + $alias = str_contains($alias, '(') ? substr($alias, strpos($alias, '(') + 1) : $alias; + $orderByAliases[] = $alias; } }