33namespace Test \Pager \Subscriber \Paginate \Doctrine ;
44
55use Doctrine \DBAL \Query \QueryBuilder ;
6+ use Doctrine \ORM \EntityManager ;
7+ use Knp \Component \Pager \ArgumentAccess \ArgumentAccessInterface ;
8+ use Knp \Component \Pager \Event \ItemsEvent ;
9+ use Knp \Component \Pager \Event \Subscriber \Paginate \Doctrine \DBALQueryBuilderSubscriber ;
610use PHPUnit \Framework \Attributes \Test ;
711use Test \Fixture \Entity \Article ;
12+ use Test \Fixture \Entity \Shop \Product ;
13+ use Test \Fixture \Entity \Shop \Tag ;
814use Test \Tool \BaseTestCaseORM ;
915
1016final class DBALQueryBuilderTest extends BaseTestCaseORM
@@ -31,9 +37,61 @@ public function shouldPaginateSimpleDoctrineQuery(): void
3137 $ this ->assertEquals ('winter ' , $ items [1 ]['title ' ]);
3238 }
3339
40+ #[Test]
41+ public function shouldStripOrderByForCount (): void
42+ {
43+ $ this ->populate ();
44+ $ qb = new QueryBuilder ($ this ->em ->getConnection ());
45+ $ qb
46+ ->select ('* ' )
47+ ->from ('Article ' , 'a ' )
48+ ->orderBy ('a.title ' );
49+
50+ $ event = new ItemsEvent (0 , 2 , $ this ->mockArgumentAccess ());
51+ $ event ->target = $ qb ;
52+
53+ $ this ->queryAnalyzer ->enable ();
54+ $ service = new DBALQueryBuilderSubscriber ($ this ->em ->getConnection ());
55+ $ service ->items ($ event );
56+ $ this ->queryAnalyzer ->disable ();
57+ $ countQuery = null ;
58+ foreach ($ this ->queryAnalyzer ->getExecutedQueries () as $ query ) {
59+ $ query = strtolower ($ query );
60+ if (str_contains ($ query , 'count(*) ' )) {
61+ $ countQuery = $ query ;
62+ break ;
63+ }
64+ }
65+
66+ $ this ->assertNotNull ($ countQuery );
67+ $ this ->assertFalse (str_contains ($ countQuery , 'order by ' ));
68+ //Ensure original query builder is not affected by the order by removal.
69+ $ this ->assertTrue (str_contains (strtolower ($ qb ->getSQL ()), 'order by ' ));
70+ }
71+
72+ #[Test]
73+ public function shouldWorkWithGroupBy (): void
74+ {
75+ $ this ->populateProducts ();
76+ $ qb = new QueryBuilder ($ this ->em ->getConnection ());
77+ $ qb
78+ ->select ('p.title, count(pt.tag_id) as totalTags ' )
79+ ->from ('Product ' , 'p ' )
80+ ->join ('p ' , 'product_tag ' , 'pt ' , 'pt.product_id = p.id ' )
81+ ->groupBy ('p.id ' );
82+
83+ $ event = new ItemsEvent (0 , 2 , $ this ->mockArgumentAccess ());
84+ $ event ->target = $ qb ;
85+
86+ $ service = new DBALQueryBuilderSubscriber ($ this ->em ->getConnection ());
87+ $ service ->items ($ event );
88+ $ this ->assertCount (2 , $ event ->items );
89+ $ this ->assertEquals (4 , $ event ->count );
90+ }
91+
3492 protected function getUsedEntityFixtures (): array
3593 {
36- return [Article::class];
94+ return [Article::class, Product::class, Tag::class ];
3795 }
3896
3997 private function populate (): void
@@ -57,4 +115,61 @@ private function populate(): void
57115 $ em ->persist ($ spring );
58116 $ em ->flush ();
59117 }
118+
119+ private function populateProducts (): void
120+ {
121+ $ em = $ this ->getMockSqliteEntityManager ();
122+ $ product = new Product ();
123+ $ product ->setTitle ('Item 1 ' );
124+ $ product ->addTag ($ this ->createTag ($ em , 'A ' ));
125+ $ product ->addTag ($ this ->createTag ($ em , 'B ' ));
126+ $ product ->addTag ($ this ->createTag ($ em , 'C ' ));
127+ $ em ->persist ($ product );
128+
129+ $ product = new Product ();
130+ $ product ->setTitle ('Item 2 ' );
131+ $ product ->addTag ($ this ->createTag ($ em , 'A ' ));
132+ $ em ->persist ($ product );
133+
134+ $ product = new Product ();
135+ $ product ->setTitle ('Item 3 ' );
136+ $ product ->addTag ($ this ->createTag ($ em , 'A ' ));
137+ $ product ->addTag ($ this ->createTag ($ em , 'B ' ));
138+ $ em ->persist ($ product );
139+
140+ $ product = new Product ();
141+ $ product ->setTitle ('Item 4 ' );
142+ $ product ->addTag ($ this ->createTag ($ em , 'A ' ));
143+ $ product ->addTag ($ this ->createTag ($ em , 'B ' ));
144+ $ product ->addTag ($ this ->createTag ($ em , 'C ' ));
145+ $ em ->persist ($ product );
146+ $ em ->flush ();
147+ $ this ->queryAnalyzer ->disable ();
148+ }
149+
150+ private function createTag (EntityManager $ em , string $ name ): Tag
151+ {
152+ $ tag = new Tag ();
153+ $ tag ->setName ($ name );
154+ $ em ->persist ($ tag );
155+
156+ return $ tag ;
157+ }
158+
159+ private function mockArgumentAccess (): ArgumentAccessInterface
160+ {
161+ return new class implements ArgumentAccessInterface {
162+ public function has (string $ name ): bool
163+ {
164+ return false ;
165+ }
166+
167+ public function get (string $ name ): string |int |float |bool |null
168+ {
169+ return null ;
170+ }
171+
172+ public function set (string $ name , float |bool |int |string |null $ value ): void {}
173+ };
174+ }
60175}
0 commit comments