From 246608a67d969b2d918c3c4f2e3b3ef238f9d8ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Wr=C3=B3blewski?= Date: Wed, 24 Jul 2024 23:26:29 +0200 Subject: [PATCH] Fix Doctrine ORM alias resolver to support multiple select clauses in the single "select()" or "addSelect()" method call --- .../Doctrine/Orm/Query/AliasResolver.php | 8 +- .../Doctrine/Orm/Query/AliasResolverTest.php | 80 ++++++++++++++++++- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/src/Bridge/Doctrine/Orm/Query/AliasResolver.php b/src/Bridge/Doctrine/Orm/Query/AliasResolver.php index 8985a345..3aa500c6 100644 --- a/src/Bridge/Doctrine/Orm/Query/AliasResolver.php +++ b/src/Bridge/Doctrine/Orm/Query/AliasResolver.php @@ -24,10 +24,12 @@ private function isResolvable(string $path, QueryBuilder $queryBuilder): bool } foreach ($queryBuilder->getDQLPart('select') ?? [] as $select) { - $parts = preg_split('/ as( hidden)? /i', $select->getParts()[0]); + foreach ($select->getParts() as $clause) { + preg_match_all('/([^,]+)\s+AS\s+(HIDDEN\s+)?([^,]+)?/i', $clause, $matches); - if ($path === ($parts[1] ?? $parts[0])) { - return false; + if (in_array($path, $matches[3])) { + return false; + } } } diff --git a/tests/Unit/Bridge/Doctrine/Orm/Query/AliasResolverTest.php b/tests/Unit/Bridge/Doctrine/Orm/Query/AliasResolverTest.php index 2d6569d1..f791d2e5 100644 --- a/tests/Unit/Bridge/Doctrine/Orm/Query/AliasResolverTest.php +++ b/tests/Unit/Bridge/Doctrine/Orm/Query/AliasResolverTest.php @@ -45,22 +45,96 @@ public static function provideResolveCases(): iterable 'category.name', ]; - yield 'With query path present in SELECT part' => [ + yield 'With query path present in SELECT clause' => [ TestEntityManagerFactory::create() ->createQueryBuilder() ->addSelect('UPPER(product.name) AS product_name') + ->from(Product::class, 'product'), + 'product_name', + 'product_name', + ]; + + yield 'With query path present in SELECT clause, marked as HIDDEN' => [ + TestEntityManagerFactory::create() + ->createQueryBuilder() + ->addSelect('UPPER(product.name) AS HIDDEN product_name') + ->from(Product::class, 'product'), + 'product_name', + 'product_name', + ]; + + yield 'With multiple selects in single call, first match' => [ + TestEntityManagerFactory::create() + ->createQueryBuilder() + ->addSelect('UPPER(product.name) AS product_name', 'category.name AS HIDDEN category_name') ->from(Product::class, 'product') ->leftJoin('product.category', 'category'), 'product_name', 'product_name', ]; - yield 'With query path present in SELECT part marked as HIDDEN' => [ + yield 'With multiple selects in single call, second match' => [ TestEntityManagerFactory::create() ->createQueryBuilder() - ->addSelect('UPPER(product.name) AS HIDDEN product_name') + ->addSelect('UPPER(product.name) AS product_name', 'category.name AS HIDDEN category_name') + ->from(Product::class, 'product') + ->leftJoin('product.category', 'category'), + 'category_name', + 'category_name', + ]; + + yield 'With multiple selects in single clause, first match' => [ + TestEntityManagerFactory::create() + ->createQueryBuilder() + ->addSelect('UPPER(product.name) AS product_name, category.name AS HIDDEN category_name') + ->from(Product::class, 'product') + ->leftJoin('product.category', 'category'), + 'product_name', + 'product_name', + ]; + + yield 'With multiple selects in single clause, second match' => [ + TestEntityManagerFactory::create() + ->createQueryBuilder() + ->addSelect('UPPER(product.name) AS product_name, category.name AS HIDDEN category_name') ->from(Product::class, 'product') ->leftJoin('product.category', 'category'), + 'category_name', + 'category_name', + ]; + + yield 'With lowercase AS' => [ + TestEntityManagerFactory::create() + ->createQueryBuilder() + ->addSelect('UPPER(product.name) as product_name') + ->from(Product::class, 'product'), + 'product_name', + 'product_name', + ]; + + yield 'With lowercase AS HIDDEN' => [ + TestEntityManagerFactory::create() + ->createQueryBuilder() + ->addSelect('UPPER(product.name) as hidden product_name') + ->from(Product::class, 'product'), + 'product_name', + 'product_name', + ]; + + yield 'With mixed case AS' => [ + TestEntityManagerFactory::create() + ->createQueryBuilder() + ->addSelect('UPPER(product.name) As product_name') + ->from(Product::class, 'product'), + 'product_name', + 'product_name', + ]; + + yield 'With mixed case AS HIDDEN' => [ + TestEntityManagerFactory::create() + ->createQueryBuilder() + ->addSelect('UPPER(product.name) aS HIDdeN product_name') + ->from(Product::class, 'product'), 'product_name', 'product_name', ];