API Platform Core has native support for paged collections. Pagination is enabled by default for all collections. Each collections contains 30 items per page. The activation of the pagination and the number of elements per page can be configured from:
When issuing a GET
request on a collection containing more than 1 page (here /books
), a Hydra collection
is returned. It’s a valid JSON(-LD) document containing items of the requested page and metadata.
{
"@context": "/contexts/Book",
"@id": "/books",
"@type": "hydra:Collection",
"hydra:member": [
{
"@id": "/books/1",
"@type": "http://schema.org/Book",
"name": "My awesome book"
},
{
"_": "Other items in the collection..."
},
],
"hydra:totalItems": 50,
"hydra:view": {
"@id": "/books?page=1",
"@type": "hydra:PartialCollectionView",
"hydra:first": "/books?page=1",
"hydra:last": "/books?page=2",
"hydra:next": "/books?page=2"
}
}
Hypermedia links to the first, the last, previous and the next page in the collection are displayed as well as the number of total items in the collection.
The name of the page parameter can be changed with the following configuration:
# api/config/packages/api_platform.yaml
api_platform:
collection:
pagination:
page_parameter_name: _page
Paginating collections is generally accepted as a good practice. It allows browsing large collections without too much overhead as well as preventing DOS attacks. However, for small collections, it can be convenient to fully disable the pagination.
The pagination can be disabled for all resources using this configuration:
# api/config/packages/api_platform.yaml
api_platform:
collection:
pagination:
enabled: false
It can also be disabled for specific resource:
<?php
// src/Entity/Book.php
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(attributes={"pagination_enabled"=false})
*/
class Book
{
// ...
}
You can configure API Platform Core to let the client enable or disable the pagination. To activate this feature globally, use the following configuration:
# api/config/packages/api_platform.yaml
api_platform:
collection:
pagination:
client_enabled: true
enabled_parameter_name: pagination # optional
The pagination can now be enabled or disabled by adding a query parameter named pagination
:
GET /books?pagination=false
: disabledGET /books?pagination=true
: enabledAny value accepted by the FILTER_VALIDATE_BOOLEAN
filter can be
used as the value.
The client ability to disable the pagination can also be set in the resource configuration:
<?php
// src/Entity/Book.php
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(attributes={"pagination_client_enabled"=true})
*/
class Book
{
// ...
}
In the same manner, the number of items per page is configurable and can be set client-side.
The number of items per page can be configured for all resources:
# api/config/packages/api_platform.yaml
api_platform:
collection:
pagination:
items_per_page: 30 # Default value
<?php
// src/Entity/Book.php
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(attributes={"pagination_items_per_page"=30})
*/
class Book
{
// ...
}
# api/config/packages/api_platform.yaml
api_platform:
collection:
pagination:
client_items_per_page: true # Disabled by default
items_per_page_parameter_name: itemsPerPage # Default value
The number of items per page can now be changed adding a query parameter named itemsPerPage
: GET /books?itemsPerPage=20
Changing the number of items per page can be enabled (or disabled) for a specific resource:
<?php
// src/Entity/Book.php
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(attributes={"pagination_client_items_per_page"=true})
*/
class Book
{
// ...
}
The number of maximum items per page can be configured for all resources:
# api/config/packages/api_platform.yaml
api_platform:
collection:
pagination:
maximum_items_per_page: 50
<?php
// src/Entity/Book.php
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(
* attributes={"maximum_items_per_page"=50}
* )
*/
class Book
{
// ...
}
<?php
// src/Entity/Book.php
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(
* collectionOperations={
* "get"={"maximum_items_per_page"=50}
* }
* )
*/
class Book
{
// ...
}
When using the default pagination, a COUNT
query will be issued against the current requested collection. This may have a
performance impact on really big collections. The downside is that the information about the last page is lost (ie: hydra:last
).
The partial pagination retrieval can be configured for all resources:
# api/config/packages/api_platform.yaml
api_platform:
collection:
pagination:
partial: true # Disabled by default
<?php
// src/Entity/Book.php
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(attributes={"pagination_partial"=true})
*/
class Book
{
// ...
}
# api/config/packages/api_platform.yaml
api_platform:
collection:
pagination:
client_partial: true # Disabled by default
partial_parameter_name: 'partial' # Default value
The partial pagination retrieval can now be changed by toggling a query parameter named partial
: GET /books?partial=true
<?php
// src/Entity/Book.php
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(attributes={"pagination_client_partial"=true})
*/
class Book
{
// ...
}
By default, pagination assumes that there will be collection fetched on a resource and thus will set useFetchJoinCollection
to true
on the Doctrine Paginator class. Having this option imply that 2 sql requests will be executed (so this avoid having less results than expected).
In most cases, even without collection on the resource, this parameter has little impact on performance. However when fetching a lot of results per page it can be counter productive.
That’s why this behavior can be configured with the pagination_fetch_join_collection
parameter on a resource:
<?php
// src/Entity/Book.php
use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(attributes={"pagination_fetch_join_collection"=false})
*/
class Book
{
// ...
}
Please note that this parameter will always be forced to false when the resource have composite keys due to a bug in doctrine
In case you’re using a custom controller action make sure you return the Paginator
object to get the full hydra response with hydra:view
(which contains information about first, last, next and previous page). The following examples show how to handle it within a repository method. The controller needs to pass through the page number. You will need to use the Doctrine Paginator and pass it to the API Platform Paginator.
First example:
<?php
//src/Repository/BookRepository.php
namespace App\Repository;
use App\Entity\Book;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Paginator;
use Doctrine\Common\Collections\Criteria;
class BookRepository extends ServiceEntityRepository
{
const ITEMS_PER_PAGE = 20;
private $tokenStorage;
public function __construct(
RegistryInterface $registry,
TokenStorageInterface $tokenStorage
) {
$this->tokenStorage = $tokenStorage;
parent::__construct($registry, Book::class);
}
public function getBooksByFavoriteAuthor(int $page = 1): Paginator
{
$firstResult = ($page -1) * self::ITEMS_PER_PAGE;
$user = $this->tokenStorage->getToken()->getUser();
$queryBuilder = $this->createQueryBuilder();
$queryBuilder->select('b')
->from(Book::class, 'b')
->where('b.author = :author')
->setParameter('author', $user->getFavoriteAuthor()->getId())
->andWhere('b.publicatedOn IS NOT NULL');
$criteria = Criteria::create()
->setFirstResult($firstResult)
->setMaxResults(self::ITEMS_PER_PAGE);
$queryBuilder->addCriteria($criteria);
$doctrinePaginator = new DoctrinePaginator($queryBuilder);
$paginator = new Paginator($doctrinePaginator);
return $paginator;
}
}
The Controller would look like this:
<?php
// src/Controller/Book/GetBooksByFavoriteAuthorAction.php
namespace App\Controller\Book;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Paginator;
use App\Repository\BookRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
class GetBooksByFavoriteAuthorAction extends AbstractController
{
public function __invoke(Request $request, BookRepository $bookRepository): Paginator
{
$page = (int) $request->query->get('page', 1);
return $bookRepository->getBooksByFavoriteAuthor($page);
}
}
The service need to use the proper repository method. You can also use the Query object inside the repository method and pass it to the Paginator instead of passing the QueryBuilder and using Criteria. Second Example:
<?php
//src/Repository/BookRepository.php
namespace App\Repository;
// use...
class BookRepository extends ServiceEntityRepository
{
// constant, variables and constructor...
public function getBooksByFavoriteAuthor(int $page = 1): Paginator
{
$firstResult = ($page -1) * self::ITEMS_PER_PAGE;
$user = $this->tokenStorage->getToken()->getUser();
$queryBuilder = $this->createQueryBuilder();
$queryBuilder->select('b')
->from(Book::class, 'b')
->where('b.author = :author')
->setParameter('author', $user->getFavoriteAuthor()->getId())
->andWhere('b.publicatedOn IS NOT NULL');
$query = $queryBuilder->getQuery()
->setFirstResult($firstResult)
->setMaxResults(self::ITEMS_PER_PAGE);
$doctrinePaginator = new DoctrinePaginator($query);
$paginator = new Paginator($doctrinePaginator);
return $paginator;
}
}
Made with love by
Les-Tilleuls.coop can help you design and develop your APIs and web projects, and train your teams in API Platform, Symfony, Next.js, Kubernetes and a wide range of other technologies.
Learn more