Support offset/slice in pagination
Description
I would like to use API Platform to get a slice of the results, not a page.
Example
/api/...?offset=422&items_per_page=81
A javascript library I'm using for infinite scrolling fetches more than a "page". Currently within API Platform, the offset is calculated from the page and returned, I'd like to override what's returned.
$firstResult = ($page - 1) * $itemsPerPage;
// ...
return [$firstResult, $itemsPerPage];
I considered extending the Doctrine ORM paginator class, but it's marked as final.
There's probably an elegant solution here somewhere, but I can't find it, so maybe it can be added. What I want is identical to the Doctrine ORM provider with pagination, so ideally I'd like to leverage the existing code and simply change the first result offset.
@dunglas , what do you think of a PR that adds "offset" (or "starting_at") to the arguments, then
Currently the pagination extension that returns a slice of the results given page_number and records_per_page, e.g. page 3, 50 records, getPagination() return [100, 50]
// vendor/api-platform/core/src/Bridge/Doctrine/Orm/Extension/PaginationExtension.php:121
public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = [])
{
if (null === $pagination = $this->getPagination($queryBuilder, $resourceClass, $operationName, $context)) {
return;
}
[$offset, $limit] = $pagination;
$queryBuilder
->setFirstResult($offset)
->setMaxResults($limit);
}
What I want is just a slice, not really a paginator. I'm not sure if the best approach is to override the paginator, or create a custom data collector, but what about building it the paginator?
['arg_name' => 'offset', 'type' => 'int', 'default' => 0],
['arg_name' => 'offsetParameterName', 'type' => 'string', 'default' => 'offset'],
['arg_name' => 'enabledOffset', 'type' => 'boolean', 'default' => false],
Then if offset were enabled, the paginator would return the values passed in and not need to calculate the offset / limit as it does now given the page number.
public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = [])
{
// if offset it enabled, use it, otherwise call the paginator..
[$offset, $limit] = [41, 220];
$queryBuilder
->setFirstResult($offset)
->setMaxResults($limit);
}
I've asked this on stackoverflow, too.
https://stackoverflow.com/questions/72583686/override-doctrine-pagination-to-perform-slice-in-api-platform-paginationextensio
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
@tacman plz I need the same aspect, the param was added or not ?
No, and I don't know how to override it. @dunglas , would you accept a PR if this were added? Or provide direction in how to implement it with a custom paginator?
Hello! I've implement a workaround for that: 1- disable pagination. 2- use an extension on QueryCollectionExtensionInterface where get the param from request.
<?php
namespace App\Extension;
use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use ApiPlatform\Metadata\CollectionOperationInterface;
final class CurrentCollectionExtension implements QueryCollectionExtensionInterface
{
public function __construct()
{
}
public function applyToCollection(
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
Operation $operation = null,
array $context = []
): void {
if ($operation instanceof CollectionOperationInterface) {
$this->addOffsetLimit($queryBuilder, $context);
}
}
private function addOffsetLimit(QueryBuilder $queryBuilder, array $context = []): void
{
$offset = $context['filters']['offset'] ?? 0;
$limit = $context['filters']['limit'];
$queryBuilder->setFirstResult($offset)
->setMaxResults($limit);
}
}
3- decorate OpenApiFactory to add limit and offset for params in query for every collection operation.
public function __invoke(array $context = []): OpenApi
{
$openApi = $this->decorated->__invoke($context);
$openApiPaths = $openApi->getPaths();
foreach (array_keys($openApiPaths->getPaths()) as $path) {
$pathItem = $openApiPaths->getPath($path);
$operation = $pathItem->getGet();
if ($operation !== null && str_ends_with($operation->getOperationId(), '_get_collection')) {
$openApiPaths->addPath(
$path,
$pathItem->withGet(
$operation->withParameters(array_merge(
[new Model\Parameter('limit', 'query', '', false, false, true, ['type' => 'integer']),
new Model\Parameter('offset', 'query', '', false, false, true, ['type' => 'integer'])],
$operation->getParameters()
))
)
);
}
}
return $openApi;
}
And do not forget to add the extension in the service config
App\Extension\CurrentCollectionExtension:
tags:
- { name: api_platform.collection_extension, priority: 100 }
Hope it helps.