-
-
Notifications
You must be signed in to change notification settings - Fork 901
/
Copy pathcustom-pagination.php
176 lines (153 loc) · 5.89 KB
/
custom-pagination.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
<?php
// ---
// slug: custom-pagination
// name: Custom pagination
// executable: true
// position: 12
// tags: expert
// ---
// In case you're using a custom collection (through a Provider), make sure you return the `Paginator` object to get the full hydra response with `view` (which contains information about first, last, next and previous page).
//
// The following example shows how to handle it using a custom Provider. You will need to use the Doctrine Paginator and pass it to the API Platform Paginator.
namespace App\Entity {
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use App\Repository\BookRepository;
use App\State\BooksListProvider;
use Doctrine\ORM\Mapping as ORM;
/* Use custom Provider on operation to retrieve the custom collection */
#[ApiResource(
operations: [
new GetCollection(provider: BooksListProvider::class),
]
)]
#[ORM\Entity(repositoryClass: BookRepository::class)]
class Book
{
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
public ?int $id = null;
#[ORM\Column]
public ?string $title = null;
#[ORM\Column(name: 'is_published', type: 'boolean')]
public ?bool $published = null;
}
}
namespace App\Repository {
use App\Entity\Book;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator;
use Doctrine\Persistence\ManagerRegistry;
class BookRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Book::class);
}
public function getPublishedBooks(int $page = 1, int $itemsPerPage = 30): DoctrinePaginator
{
/* Retrieve the custom collection and inject it into a Doctrine Paginator object */
return new DoctrinePaginator(
$this->createQueryBuilder('b')
->where('b.published = :isPublished')
->setParameter('isPublished', true)
->addCriteria(
Criteria::create()
->setFirstResult(($page - 1) * $itemsPerPage)
->setMaxResults($itemsPerPage)
)
);
}
}
}
namespace App\State {
use ApiPlatform\Doctrine\Orm\Paginator;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\Pagination\Pagination;
use ApiPlatform\State\ProviderInterface;
use App\Repository\BookRepository;
class BooksListProvider implements ProviderInterface
{
public function __construct(private readonly BookRepository $bookRepository, private readonly Pagination $pagination)
{
}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): Paginator
{
/* Retrieve the pagination parameters from the context thanks to the Pagination object */
[$page, , $limit] = $this->pagination->getPagination($operation, $context);
/* Decorates the Doctrine Paginator object to the API Platform Paginator one */
return new Paginator($this->bookRepository->getPublishedBooks($page, $limit));
}
}
}
namespace App\Playground {
use Symfony\Component\HttpFoundation\Request;
function request(): Request
{
return Request::create('/books.jsonld', 'GET');
}
}
namespace DoctrineMigrations {
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Migration extends AbstractMigration
{
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE book (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, title VARCHAR(255) NOT NULL, is_published SMALLINT NOT NULL)');
}
}
}
namespace App\Fixtures {
use App\Entity\Book;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
use Zenstruck\Foundry\AnonymousFactory;
use function Zenstruck\Foundry\faker;
final class BookFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
/* Create books published or not */
$factory = AnonymousFactory::new(Book::class);
$factory->many(5)->create(static function (int $i): array {
return [
'title' => faker()->title(),
'published' => false,
];
});
$factory->many(35)->create(static function (int $i): array {
return [
'title' => faker()->title(),
'published' => true,
];
});
}
}
}
namespace App\Tests {
use ApiPlatform\Playground\Test\TestGuideTrait;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use App\Entity\Book;
final class BookTest extends ApiTestCase
{
use TestGuideTrait;
public function testTheCustomCollectionIsPaginated(): void
{
$response = static::createClient()->request('GET', '/books.jsonld');
$this->assertResponseIsSuccessful();
$this->assertMatchesResourceCollectionJsonSchema(Book::class, '_api_/books{._format}_get_collection', 'jsonld');
$this->assertNotSame(0, $response->toArray(false)['totalItems'], 'The collection is empty.');
$this->assertJsonContains([
'totalItems' => 35,
'view' => [
'@id' => '/books.jsonld?page=1',
'@type' => 'PartialCollectionView',
'first' => '/books.jsonld?page=1',
'last' => '/books.jsonld?page=2',
'next' => '/books.jsonld?page=2',
],
]);
}
}
}