Skip to content

Commit fc3db4d

Browse files
authored
Merge pull request #5 from veewee/custom-pager
Use custom window-count based pager over pagerfanta
2 parents 121c3f8 + d8236a1 commit fc3db4d

26 files changed

+1075
-290
lines changed

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,34 @@ App\Domain\Model\User:
608608
"id": !php/enum App\Infrastructure\Doctrine\Schema\User\UsersTableColumns::Id
609609
```
610610

611+
# Pagination
612+
613+
Dealing with pagination is a task that is often needed.
614+
This package contains some tools to help you with that:
615+
616+
```php
617+
use Phpro\DbalTools\Pager\MappingPager;
618+
use Phpro\DbalTools\Pager\Pager;
619+
use Phpro\DbalTools\Pager\Pagination;
620+
use Phpro\DbalTools\Pager\WindowCountPager;
621+
622+
$query = $connection->createQueryBuilder()->select('*')->from('users');
623+
$userMapper = new UsersMapper()->convertFromDb(...);
624+
625+
$usersPager = new MappingPager(
626+
WindowCountPager::create(
627+
new Pagination(page: $page limit: $limit),
628+
$query,
629+
),
630+
$userMapper,
631+
);
632+
633+
// Iterating over the results:
634+
$totalResults = $usersPager->totalResults();
635+
$totalPages = $usersPager->totalPages();
636+
$users = [...$usersPager];
637+
```
638+
611639
# Building queries
612640

613641
This package contains a set of tools that helps you build queries based on partial SQL Expressions.

composer.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@
3131
"symfony/translation": "^7.2",
3232
"symfony/validator": "^7.2",
3333
"doctrine/doctrine-migrations-bundle": "^3.4",
34-
"doctrine/doctrine-bundle": "^2.14",
35-
"pagerfanta/core": "^4.7",
36-
"pagerfanta/doctrine-dbal-adapter": "^4.7"
34+
"doctrine/doctrine-bundle": "^2.14"
3735
},
3836
"require-dev": {
3937
"phpunit/phpunit": "^12.1.3",
@@ -44,7 +42,8 @@
4442
"php-cs-fixer/shim": "^3.75",
4543
"phpro/grumphp-shim": "^2.12",
4644
"amphp/dns": "^2.4",
47-
"amphp/socket": "^2.3.1"
45+
"amphp/socket": "^2.3.1",
46+
"ramsey/uuid": "^4.9"
4847
},
4948
"config": {
5049
"allow-plugins": {

src/Expression/Over.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phpro\DbalTools\Expression;
6+
7+
final readonly class Over implements Expression
8+
{
9+
public function __construct(
10+
private ?Expression $expression,
11+
) {
12+
}
13+
14+
public static function fullWindow(): self
15+
{
16+
return new self(null);
17+
}
18+
19+
public static function partition(PartitionBy $partitionBy, ?OrderBy $orderBy = null): self
20+
{
21+
return new self(
22+
Expressions::fromNullable($partitionBy, $orderBy)->join(' ')
23+
);
24+
}
25+
26+
public static function orderBy(OrderBy $orderBy): self
27+
{
28+
return new self($orderBy);
29+
}
30+
31+
public static function aggregation(
32+
Expression $aggregation,
33+
self $over,
34+
): Expression {
35+
return new Expressions($aggregation, $over)->join(' ');
36+
}
37+
38+
public function toSQL(): string
39+
{
40+
return sprintf('OVER(%s)', $this->expression?->toSQL() ?? '');
41+
}
42+
}

src/Expression/PartitionBy.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phpro\DbalTools\Expression;
6+
7+
final readonly class PartitionBy implements Expression
8+
{
9+
/**
10+
* @no-named-arguments
11+
*/
12+
public function __construct(
13+
private Expression $partitionByExpression,
14+
) {
15+
}
16+
17+
/**
18+
* @return non-empty-string
19+
*/
20+
public function toSQL(): string
21+
{
22+
return sprintf('PARTITION BY %s', $this->partitionByExpression->toSQL());
23+
}
24+
}

src/Expression/RowNumber.php

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,18 @@
1010
* @no-named-arguments
1111
*/
1212
public function __construct(
13-
private ?Expression $expression = null,
13+
private ?Over $over = null,
1414
) {
1515
}
1616

17-
/**
18-
* @no-named-arguments
19-
*/
20-
public static function over(
21-
Expression $expression,
22-
Expression ...$expressions,
23-
): self {
24-
return new self(new Expressions($expression, ...$expressions)->join(' '));
25-
}
26-
2717
/**
2818
* @return non-empty-string
2919
*/
3020
public function toSQL(): string
3121
{
32-
return sprintf(
33-
'ROW_NUMBER() OVER (%s)',
34-
$this->expression?->toSQL() ?? '',
35-
);
22+
return Over::aggregation(
23+
new SqlExpression('ROW_NUMBER()'),
24+
$this->over ?? Over::fullWindow(),
25+
)->toSQL();
3626
}
3727
}

src/Pager/MappingPager.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phpro\DbalTools\Pager;
6+
7+
use function Psl\Vec\map;
8+
9+
/**
10+
* @template I
11+
* @template O
12+
*
13+
* @implements Pager<O>
14+
*/
15+
final readonly class MappingPager implements Pager
16+
{
17+
/**
18+
* @param Pager<I> $pager
19+
* @param \Closure(I): O $mapper
20+
*/
21+
public function __construct(
22+
private Pager $pager,
23+
private \Closure $mapper,
24+
) {
25+
}
26+
27+
public function pagination(): Pagination
28+
{
29+
return $this->pager->pagination();
30+
}
31+
32+
public function totalResults(): int
33+
{
34+
return $this->pager->totalResults();
35+
}
36+
37+
public function totalPages(): int
38+
{
39+
return $this->pager->totalPages();
40+
}
41+
42+
public function traverse(\Closure $mapper): iterable
43+
{
44+
return map($this, $mapper);
45+
}
46+
47+
public function getIterator(): \Traversable
48+
{
49+
foreach ($this->pager as $value) {
50+
yield ($this->mapper)($value);
51+
}
52+
}
53+
}

src/Pager/MockedPager.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phpro\DbalTools\Pager;
6+
7+
use function Psl\Math\ceil;
8+
use function Psl\Vec\map;
9+
10+
/**
11+
* @template T
12+
*
13+
* @template-implements Pager<T>
14+
*/
15+
final readonly class MockedPager implements Pager
16+
{
17+
/**
18+
* @param array<int, T> $records
19+
*/
20+
public function __construct(
21+
private Pagination $pagination,
22+
private array $records,
23+
) {
24+
}
25+
26+
public function pagination(): Pagination
27+
{
28+
return $this->pagination;
29+
}
30+
31+
public function totalResults(): int
32+
{
33+
return count($this->records);
34+
}
35+
36+
public function totalPages(): int
37+
{
38+
$totalResults = $this->totalResults();
39+
$limit = $this->pagination()->limit;
40+
if (!$totalResults) {
41+
return 1;
42+
}
43+
44+
return (int) ceil($this->totalResults() / $limit);
45+
}
46+
47+
public function traverse(\Closure $mapper): iterable
48+
{
49+
return map($this, $mapper);
50+
}
51+
52+
public function getIterator(): \Traversable
53+
{
54+
yield from array_slice(
55+
$this->records,
56+
($this->pagination()->page - 1) * $this->pagination->limit,
57+
$this->pagination()->limit
58+
);
59+
}
60+
}

src/Pager/Pager.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phpro\DbalTools\Pager;
6+
7+
/**
8+
* @template T
9+
*
10+
* @template-extends \IteratorAggregate<int, T>
11+
*/
12+
interface Pager extends \IteratorAggregate
13+
{
14+
public function pagination(): Pagination;
15+
16+
public function totalResults(): int;
17+
18+
public function totalPages(): int;
19+
20+
/**
21+
* @template O
22+
*
23+
* @param \Closure(T): O $mapper
24+
*
25+
* @return iterable<int, O>
26+
*/
27+
public function traverse(\Closure $mapper): iterable;
28+
}

src/Pager/Pagination.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Phpro\DbalTools\Pager;
6+
7+
final readonly class Pagination
8+
{
9+
/**
10+
* @param int<1,max> $page - Page number, starting from 1
11+
* @param int<1,max> $limit - Number of items per page
12+
*/
13+
public function __construct(
14+
public int $page,
15+
public int $limit,
16+
) {
17+
}
18+
}

0 commit comments

Comments
 (0)