Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Pagination

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

If we have a million, or a thousand... or even a hundred cheese listings, we can't return all of them when someone makes a GET request to /api/cheeses! The way that's solved in an API is really the same as on the web: pagination! And in API Platform... ah, it's so boring... you get a powerful, flexible pagination system... without doing... anything.

Let's go to the POST operation and create a few more cheese listings. I'll put in some simple data... and execute a bunch of times.. in fast forward. On a real project, I'd use data fixtures to help me get useful dummy data.

We should have about 10 or so thanks to the 4 we started with. Now, head up to the GET collection operation... and hit Execute. We still see all the results. That's because API Platform shows 30 results per page, by default. Because I don't feel like adding 20 more manually, this is a great time to learn how to change that!

Controlling Items Per Page

First, this can be changed globally in your config/packages/api_platform.yaml file. I won't show it now, but always remember that you can run:

php bin/console debug:config api_platform

to see a list of all of the valid configuration for that file and their current values. That would reveal a collection.pagination section that is full of config.

But we can also control the number of items per page on a resource-by-resource basis. Inside the @ApiResource annotation, add attributes={}... which is a key that holds a variety of random configuration for API Platform. And then, "pagination_items_per_page": 10.

... lines 1 - 15
/**
* @ApiResource(
... lines 18 - 25
* attributes={
* "pagination_items_per_page"=10
* }
* )
... lines 30 - 34
*/
class CheeseListing
... lines 37 - 167

I mentioned earlier that a lot of API Platform is learning exactly what you can configure inside of this annotation and how. This is a perfect example.

Go back to the docs - no need to refresh. Just hit Execute. Let's see... the total items are 11... but if you counted, this is only showing 10 results! Hello pagination! We also have a new hydra:view property. This advertises that pagination is happening and how we can "browse" through the other pages: we can follow hydra:first, hydra:last and hydra:next to go to the first, last or next page. The URLs look exactly like I want: ?page=1, ?page=2 and so on.

Open a new tab and go back to /api/cheeses.jsonld. Yep, the first 10 results. Now add ?page=2... to see the one, last result.

Filtering also still works. Try .jsonld?title=cheese. That returns... only 10 results... so no pagination! That's no fun. Let's go back to the docs, open the POST endpoint and add a few more. Oh, but let's make sure that we add one with "cheese" in the title. Hit Execute a few times.

Now go refresh the GET collection operation with ?title=cheese. Nice! We have 13 total results and this shows the first 10. What's really nice is that the pagination links include the filter! That is super useful in JavaScript: you don't need to try to hack the URL together manually by combining the page and filter information: just read the links from hydra and use them.

Next, we know that our API can return JSON-LD, JSON and HTML. And... that's probably all we need, right? Let's see how easy it is to add more formats... including making our cheese listings downloadable as a CSV.

Leave a comment!

2
Login or Register to join the conversation
Eric Avatar
Eric Avatar Eric | posted 4 years ago | edited

In order for a client application, that is consuming a Hydra formatted JSON response, to know what the last page number is they would need to parse the "page" query parameter off the "hydra:last" string, correct?

Example: using this format it would be very easy to have pagination links to the next and previous pages. But if a client application wanted to show more page numbers available (ie: linking to pages 1, 2, 3 .... 5, and 6) they would need to parse the "page" query parameter, determine that the last page is 6 and build the links from there, correct?

Just making sure this is suggested approach as I've always passed something like the "lastPage": 6 in a JSON response containing paginated results.

Thanks!

Reply

Hey Eric

I think your approach is correct. You will have to read the last key value and build your pagination table dynamically

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial works great for Symfony 5 and API Platform 2.5/2.6.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^2.1", // v2.4.3
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // 1.10.2
        "doctrine/doctrine-bundle": "^1.6", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^2.0", // v2.0.0
        "doctrine/orm": "^2.4.5", // v2.7.2
        "nelmio/cors-bundle": "^1.5", // 1.5.5
        "nesbot/carbon": "^2.17", // 2.19.2
        "phpdocumentor/reflection-docblock": "^3.0 || ^4.0", // 4.3.1
        "symfony/asset": "4.2.*|4.3.*|4.4.*", // v4.3.11
        "symfony/console": "4.2.*", // v4.2.12
        "symfony/dotenv": "4.2.*", // v4.2.12
        "symfony/expression-language": "4.2.*|4.3.*|4.4.*", // v4.3.11
        "symfony/flex": "^1.1", // v1.17.6
        "symfony/framework-bundle": "4.2.*", // v4.2.12
        "symfony/security-bundle": "4.2.*|4.3.*", // v4.3.3
        "symfony/twig-bundle": "4.2.*|4.3.*", // v4.2.12
        "symfony/validator": "4.2.*|4.3.*", // v4.3.11
        "symfony/yaml": "4.2.*" // v4.2.12
    },
    "require-dev": {
        "symfony/maker-bundle": "^1.11", // v1.11.6
        "symfony/stopwatch": "4.2.*|4.3.*", // v4.2.9
        "symfony/web-profiler-bundle": "4.2.*|4.3.*" // v4.2.9
    }
}
userVoice