Skip to content

Commit eabf392

Browse files
authored
Merge pull request #348 from kicken/remove-order-by
Remove order by clause from the item count query.
2 parents 0fe05e6 + bed93ef commit eabf392

File tree

2 files changed

+117
-2
lines changed

2 files changed

+117
-2
lines changed

src/Knp/Component/Pager/Event/Subscriber/Paginate/Doctrine/DBALQueryBuilderSubscriber.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function items(ItemsEvent $event): void
2727
->connection
2828
->createQueryBuilder()
2929
->select('COUNT(*)')
30-
->from('(' . $target->getSQL() . ')', 'tmp')
30+
->from('(' . (clone $target)->resetOrderBy()->getSQL() . ')', 'tmp')
3131
->setParameters($target->getParameters(), $target->getParameterTypes())
3232
;
3333

tests/Test/Pager/Subscriber/Paginate/Doctrine/DBALQueryBuilderTest.php

+116-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@
33
namespace Test\Pager\Subscriber\Paginate\Doctrine;
44

55
use 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;
610
use PHPUnit\Framework\Attributes\Test;
711
use Test\Fixture\Entity\Article;
12+
use Test\Fixture\Entity\Shop\Product;
13+
use Test\Fixture\Entity\Shop\Tag;
814
use Test\Tool\BaseTestCaseORM;
915

1016
final 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

Comments
 (0)