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 $10.00

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

Login Subscribe

On production - because The SpaceBar is going to be a huge hit with a lot of thoughtful comments - this list will eventually become, way long. Not only will this page become hard to use, it will quickly slow down until it stops working. If you ever need to query and render more than 100 Doctrine entities, you're going to have slow loading times. If you try to print 1000, your page probably just won't load.

But no problem! Printing that many results is a total bummer for usability anyways! And that's why the Internet has a tried-and-true solution for this: pagination. Doctrine itself doesn't come with any pagination features. But, it doesn't need to: there are a few great libraries that do.

Search for KnpPaginatorBundle. As usual, my disclaimer is that I did nothing to help build this bundle, I just think it's great. Find the composer require line, copy that, go to your terminal and paste:

composer require "knplabs/knp-paginator-bundle:^2.7"

While that's installing, go back to its documentation. As I love to tell you, over and over again, the main thing a bundle gives you is new services. And that's 100% true for this bundle.

But before we talk more about that, notice that this has some details about enabling your bundle. That happens automatically in Symfony 4 thanks to Flex. So, ignore it.

Paginator Usage and the Autowiring Alias

Anyways, look down at the Usage example. Hmm, from a controller, it says to use $this->get('knp_paginator') to get some paginator service. Then, you pass that a query, the current page number, read from a ?page query parameter, and the number of items you want per page. The paginator handles the rest! If you want 10 results per page and you're on page 3, the paginator will fetch only the results you need by adding a LIMIT and OFFSET to your query.

The one tricky thing is that the documentation is a little bit out of date. The $this->get() method - which is the same as saying $this->container->get() - is the historic way to fetch a service out of the container by using its id. Depending on your setup, that may or may not even be possible in Symfony 4. And, in general, it's no longer considered a best-practice. Instead, you should use dependency injection, which almost always means, autowiring.

But, hmm, it doesn't say anything about autowiring here. That's a problem: the bundle needs to tell us what class or interface we can use to autowire the paginator service. Ah, don't worry: we can figure it out on our own!

Go back to your terminal Excellent! The install finished. Now run:

php bin/console debug:autowiring

Search for pager. Boom! Apparently there is a PaginatorInterface we can use to get that exact service. We are in business!

Using the Paginator

Back in CommentAdminController, add that as the 3rd argument: PaginatorInterface. Make sure to auto-complete this to get the use statement. Call the arg $paginator:

... lines 1 - 5
use Knp\Component\Pager\PaginatorInterface;
... lines 7 - 10
class CommentAdminController extends Controller
{
... lines 13 - 15
public function index(CommentRepository $repository, Request $request, PaginatorInterface $paginator)
{
... lines 18 - 30
}
}

Next, go back to the docs and copy the $pagination = section, return, and paste:

... lines 1 - 10
class CommentAdminController extends Controller
{
... lines 13 - 15
public function index(CommentRepository $repository, Request $request, PaginatorInterface $paginator)
{
... lines 18 - 21
$pagination = $paginator->paginate(
$queryBuilder, /* query NOT result */
$request->query->getInt('page', 1)/*page number*/,
10/*limit per page*/
);
... lines 27 - 30
}
}

Ok, so what should we use for $query? When you use a paginator, there is an important, practical change: we are no longer responsible for actually executing the query. Nope, we're now only responsible for building a query and passing it to the paginator. This $query variable should be a QueryBuilder.

So, back in CommentRepository, let's refactor this method to return that instead. Remove the @returns and, instead, use a QueryBuilder return type:

... lines 1 - 16
class CommentRepository extends ServiceEntityRepository
{
... lines 19 - 31
/**
* @param string|null $term
*/
public function getWithSearchQueryBuilder(?string $term): QueryBuilder
{
... lines 37 - 49
}
... lines 51 - 79
}

Next, at the bottom, remove the getQuery() and getResults() lines:

... lines 1 - 16
class CommentRepository extends ServiceEntityRepository
{
... lines 19 - 31
/**
* @param string|null $term
*/
public function getWithSearchQueryBuilder(?string $term): QueryBuilder
{
... lines 37 - 46
return $qb
->orderBy('c.createdAt', 'DESC')
;
}
... lines 51 - 79
}

Finally, rename the method to getWithSearchQueryBuilder():

... lines 1 - 16
class CommentRepository extends ServiceEntityRepository
{
... lines 19 - 34
public function getWithSearchQueryBuilder(?string $term): QueryBuilder
{
... lines 37 - 49
}
... lines 51 - 79
}

Perfect! Back in the controller, add $queryBuilder = $repository->getWithSearchQueryBuilder($q). Pass this below:

... lines 1 - 10
class CommentAdminController extends Controller
{
... lines 13 - 15
public function index(CommentRepository $repository, Request $request, PaginatorInterface $paginator)
{
... lines 18 - 19
$queryBuilder = $repository->getWithSearchQueryBuilder($q);
$pagination = $paginator->paginate(
$queryBuilder, /* query NOT result */
$request->query->getInt('page', 1)/*page number*/,
10/*limit per page*/
);
... lines 27 - 30
}
}

Finally, instead of passing comments into the template, pass this pagination variable:

... lines 1 - 10
class CommentAdminController extends Controller
{
... lines 13 - 15
public function index(CommentRepository $repository, Request $request, PaginatorInterface $paginator)
{
... lines 18 - 19
$queryBuilder = $repository->getWithSearchQueryBuilder($q);
$pagination = $paginator->paginate(
$queryBuilder, /* query NOT result */
$request->query->getInt('page', 1)/*page number*/,
10/*limit per page*/
);
return $this->render('comment_admin/index.html.twig', [
'pagination' => $pagination,
]);
}
}

Open index.html.twig so we can make changes there. First, at the top, let's print the total number of comments because we will now only show 10 on each page. To do that, go back to the docs. Ah, this is perfect. Use: pagination.getTotalItemCount():

... lines 1 - 6
{% block content_body %}
<div class="row">
<div class="col-sm-12">
<h1>Manage Comments ({{ pagination.getTotalItemCount }})</h1>
... lines 11 - 66
</div>
</div>
{% endblock %}

Next, down in the loop, update this to for comment in pagination:

... lines 1 - 6
{% block content_body %}
<div class="row">
<div class="col-sm-12">
<h1>Manage Comments ({{ pagination.getTotalItemCount }})</h1>
... lines 11 - 28
<table class="table table-striped">
... lines 30 - 37
<tbody>
{% for comment in pagination %}
... lines 40 - 61
{% endfor %}
</tbody>
</table>
... lines 65 - 66
</div>
</div>
{% endblock %}

Yes, pagination is an object. But, you can loop over it to get the comments for the current page only.

Oh, and at the bottom, we need some navigation to help the user go to the other pages. That's really easy: on the docs, copy the knp_pagination_render() line and, paste!

... lines 1 - 6
{% block content_body %}
<div class="row">
<div class="col-sm-12">
<h1>Manage Comments ({{ pagination.getTotalItemCount }})</h1>
... lines 11 - 28
<table class="table table-striped">
... lines 30 - 37
<tbody>
{% for comment in pagination %}
... lines 40 - 61
{% endfor %}
</tbody>
</table>
{{ knp_pagination_render(pagination) }}
</div>
</div>
{% endblock %}

Phew! Let's go check it out! Yes! 100 total results, but only 10 on this page. We can click to page 2, then 3 and so-on. Heck, the search even works! Try something really common, like est. The URL has the ?q= query parameter. And, if you change pages, it stays: this is page 2 of that search. Dang, that's awesome.

Using the Bootstrap Pager Navigation Theme

Of course, there's one super minor problem... um... dang, that navigation looks horrible. But, we can fix that! The bundle comes with a bunch of different themes for the navigation. Scroll back up to the configuration example. Obviously, you don't need to configure anything on this bundle. But, there are several options. The most important one is this: template.pagination. This determines which template is used to build the navigation links. And, it ships with one for Bootstrap 4, which is what we're using. Booya!

So, first question: where should this configuration live? Sometimes, a recipe will create a file for us, like stof_doctrine_extensions.yaml. But in this case, that didn't happen. And that's ok! Not every bundle needs to give you a config file. Just create it by hand: knp_paginator.yaml.

As usual, the filename matches the root config key. But, we know from previous tutorials that the filename is actually meaningless. Next, copy the config down to the pagination line, move over, paste, then remove all the stuff we don't need. Finally, copy the bootstrap v4 template name and, paste:

knp_paginator:
template:
pagination: '@KnpPaginator/Pagination/twitter_bootstrap_v4_pagination.html.twig'

We're ready! Move back and refresh. Boom! It still works! It's beautiful! We rock! Our pagination is awesome! I'm super happy!

Now that this is perfect, let's turn to our last big topic: generating a totally new, ManyToMany relationship.

Leave a comment!

84
Login or Register to join the conversation
Dion V. Avatar
Dion V. Avatar Dion V. | posted 3 years ago

I had a question but it figured itself out.

Anyways, thanks for the great tutorials!!

2 Reply

Is it posible, and how to make infinite scroll with KnpPaginatorBundle

1 Reply

Hey sasa1007!

For sure! I mean, the implementation depends on what JavaScript library you're using, but it's absolutely possible. Infinite scroll works like this:

A) When you scroll a certain distance, an AJAX request is made to get the "next" results (e.g. the next 20 results)
B) When that AJAX request finishes, those results are added to the bottom of your list, so you can keep scrolling

Behind the scenes, this is no different than pagination. The only differences is that you're initiating pagination via a scroll event (instead of clicking a link) and you're putting the results on the bottom of the list instead replacing the current list.

So here's how it might work:

1) When you user scrolls to a certain point, you start the AJAX request. You would set some JavaScript variable that keeps track of the current "page" - 1 by default. First, increment that to 2. Then, make an AJAX request to an endpoint - e.g. /articles/{page}. This AJAX endpoint would read the page you're sending it and you would set up pagination like normal. You would then render a template and render the results (but without an "extends" - you just want the result, no the page template). Alternatively, you could return the "current results" as JSON.

2) You then read the data back, put it at the bottom of your "articles list" and increment the JavaScript "page" variable to 3. The next time they scroll down far enough, they would make an AJAX request to /articles/3.

Let me know if that makes sense!

Cheers!

1 Reply

Hi Ryan,

Thank you for your response.

I thought it would be easier.

Its html template that using https://infinite-scroll.com/

So on Infinite Scroll documentation they say that only need to Initialize it,

$('.container').infiniteScroll({
// options
path: '.pagination__next', // this is problem
append: '.post',
history: false,
});

it work when i hardcoded

http://localhost:8000/news/?page=2

but only for that page

1 Reply

Hey sasa1007!

Try something like this:




$('.container').infiniteScroll({
    path: '{{ path('news_route_name') }}?page={{ '{{#}}' }}',
    append: '.post',
    history: false,
});

I know, this looks a little crazy :). According to their docs, to pass "path" a URL, you need to use the form "/news?page={{#}}" - and then the library will replace the <code{{#}} part with whatever the next page number should be. So, above, I'm first printing the URL to the /news page using the path() function (replace news_route_name` with your actual route name). Then I'm adding ?page={{#}}. However, because {{#}} looks like Twig code, this will cause a Twig error. That's why I actually use Twig to print this string - which looks totally weird, I realize :).

The code you had before - path: '.pagination__next' is also a valid way to do it - but for this to work, you need to ALWAYS have a link on the page with a pagination__next class on it whose URL points to the next page URL (e.g. /news?page=3).

Cheers!

1 Reply

Thanks,

It is not working with

$('.container').infiniteScroll({
path: '{{ path('news_route_name') }}?page={{ '{{#}}' }}',
append: '.post',
history: false,
});

but work with

$('.container').infiniteScroll({
path: '{{ path('news_route_name') }}?page={{ 0 }}',
append: '.post',
history: false,
});

One more time thank you for help

and one more thing he show all posts (news, never mind) until last one, but when I continue to scroll I get more and more ajax requests

.....
5342ad GET xhr 200 /vesti/?page=38 106ms
1430e4 GET xhr 200 /vesti/?page=39 103ms
580b47 GET xhr 200 /vesti/?page=40 108ms
20748a GET xhr 200 /vesti/?page=41 106ms
d9fca0 GET xhr 200 /vesti/?page=42 98ms

(endless)
.......

1 Reply

Hey sasa1007!

Awesome! Thank you for sharing your working solution :).

> and one more thing he show all posts (news, never mind) until last one, but when I continue to scroll I get more and more ajax requests

Hmm, I'm not sure about this. It seems like InfiniteScroll should be smart enough to realize when it has reached the final page (because the endpoint is returning 0 results) and stop making more requests. If there is a fix for this, it's probably something related to that JS library - it looks like you're doing everything correctly to me.

Cheers!

1 Reply
Mike P. Avatar

Thank you very much!

Reply
Steve-D Avatar
Steve-D Avatar Steve-D | posted 4 years ago

Hi

I'm trying to tidy up the url so that ?page=3 becomes /3/, is there anyway to do this?

Cheers

Steve

1 Reply

Hey Steve D.!

Yes, buuuuuut, maybe you don't want to do it :). Let me explain both parts:

1) To get a clean /3/ URL, change the Route to be /admin/comment/{page}. I could be wrong, but if I remember correctly, the paginator will be smart enough to generate the URLs correctly by doing this.

2) This is a FINE solution. However, if you later also want to add some filtering/searching, you'll probably do that by adding a ?q= to the end of the URL. If you ALSO try to put this into the URL, then suddenly things start to get a bit ugly - should you do /admin/comment/{search}/{page} or /admin/comment/{page}/{search}? In general, that's a bit why these are often put as query parameters. But also, what I'm saying is more true for APIs, where pretty URLs aren't important at all. So yes, in this case, making the URL is pretty - but I wouldn't do it in an API :).

Cheers! Oh, and let me know if I'm wrong about (1) and the paginator doesn't generate the URLs the way you want.

1 Reply
Steve-D Avatar

Hi Ryan

You're spot on, it does do that. I'm already using a search query (as in the video) and have the search parameter in the url which makes point 2 very valid, its not right. Plus, if I've navigated to a page then search for an item it fails to return the correct results. I've always used DataTables but with huge records the filter can be a bit of pain thus wanting to try Paginator

... Hold the front page...

I've changed the search term to be a post variable rather than get variable and if there is a search term present then the page number is reset to 1. I've also set a session var to store the search term so that it is not lost when navigating pages, this gets cleared when the reset is clicked or a new search term is used. Happy days.

Thanks again Ryan

Steve

Reply

Hey Steve D.

Using POST method for only fetching information is not right, a POST request is meant for updating the system, such as a record on the database, so you is better to use a GET request for this task and use query parameters to avoid storing data into the user's session

Cheers!

Reply
Steve-D Avatar

Very true Diego, and I've gone back to using GET in this instance but have an application that does store search queries so I guess POST would be applicable :)

Thank you for your assistance

Steve

Reply
Steve-D Avatar

It seems this is not going to work, there are issues with other actions in the controller as they match the route of index action. Never mind

Reply
Peter-K Avatar
Peter-K Avatar Peter-K | posted 5 years ago | edited

All is working except my pagination labels for previous & next are : label_previous & label_next also on profiler bar I have 2 translation errors:
<br />en KnpPaginatorBundle 1 label_previous label_previous<br />en KnpPaginatorBundle 1 label_next label_next<br />

so I was checking documentation and there is:
`
Troubleshooting
Make sure the translator is activated in your symfony config :
framework:

translator: { fallbacks: ['%locale%'] }

If your locale is not available, create your own translation file in app/Resources/translations/KnpPaginatorBundle.en.yml (substitute en for your own language code if needed) . Then add these lines:
label_next: Next
label_previous: Previous
`

but I dont want to break it so I will wait for translation screencast and then fix it.

1 Reply

Hey Peter K.

Don't be too afraid of playing around with Symfon :)
Look's like you only need to activate translations. If you are on Symfony4, you have even less work to do (thanks to Symfony Flex): http://symfony.com/doc/4.0/...

Cheers!

2 Reply
Peter-K Avatar
Peter-K Avatar Peter-K | MolloKhan | posted 5 years ago | edited

For beginners like me:
run "composer require symfony/translation"
then create file "translations/KnpPaginatorBundle.en.yaml"
paste there
<br />label_next: Next<br />label_previous: Previous<br />
Then I had to run "php bin/console cache:clear"

That seems to do the trick and everything is sorted.

5 Reply

Oh, yes, whenever you create a new translation file you have to clear the cache. Thanks for sharing the steps!

2 Reply

Or at least, I think you may need to clear cache for the FIRST translation file - not sure for the 2nd and 3rd - Symfony *should* see those automatically.

Reply
Mike P. Avatar

I had to clear the cache as well, before it didn't work.

Shortcut:
./bin/console c:c

1 Reply
Peter L. Avatar
Peter L. Avatar Peter L. | posted 1 year ago

"Doctrine itself doesn't come with any pagination features."
"Doctrine\ORM\Tools\Pagination\Paginator" is there at least from 2013 (version 2.4).
It is very simple to compare with KnpPagination Bundle, but it is there.
This pure doctrine paginator is also used in the Symfony FastTrack book.

Reply

Hey Peter L.!

You're absolutely correct! The reason I typically use an extra library on top of that is because the Doctrine paginator doesn't make it easy to determine what the "next" page is (or if one is needed) or the previous page - the book calculates those manually. That's not a *huge* problem, but it's always felt better to have a library handle those things for me.

Cheers!

Reply
Miky Avatar

Hi, and want ask from another side. If we are using API Platform (currently it this time v2.6+) should we use somehow the pagination from it without that we will call api request ? Mean by use statement in controller of some part of Api Platfrom ?

Reply

Hey Miky!

You absolutely *can* choose to do this. But it's not SO easy (there is not one simply function you can call to re-use the paginator). In API Platform, they actually use the core Doctrine paginator. This is basically the logic here: https://github.com/api-plat...

They basically (A) create a QueryBuilder then (B) in a different part of the code they set the max results and limit https://github.com/api-plat...

But there are a few problems with this:

1) You can't just re-use the code from API Platform, you would be copying it.
2) The Doctrine paginator is very low-level: it's your job to look at the current page number and calculate the "offset" that should be used. It also gives you no help for generating things on the frontend (like next/prev links).

So... the answer is that... you "sort" of can re-use the paginator from ApiPlatform, but there is probably not aa huge advantage to doing so :).

Cheers!

Reply
Farshad Avatar
Farshad Avatar Farshad | posted 2 years ago

I did the exact steps but adding bootstrap didn't work.

Reply

Hey @Farry7

What exactly not working? styling or HTML markup? do you have any errors in console?

Cheers!

Reply
Farshad Avatar
Farshad Avatar Farshad | posted 2 years ago

I tried installing paginator and got this error. However, the pagination works (except bootstrap). I think the paginator was already installed with knp?

Script security-checker security:check returned with error code 1
!!
!! The web service failed for an unknown reason (HTTP 403).
!!
!!
Script @auto-scripts was called via post-update-cmd

Installation failed, reverting ./composer.json to its original content.

Reply

Hey @Farry7

I'd recommend you to remove sensiolabs/security-checker from you project, BTW this bundle is abandoned, and if you download fresh code for this tutorial you will not find it =)

Cheers!

Reply
Default user avatar

And what about https://www.doctrine-projec...
I think this tutorial have to be updated with provided reference because it out of date.

Reply

Hey rtwent,

That is native Doctrine pagination, so that's a different thing. Yes, both solves pagination problem, but in this video we use a third-party bundle for this called knplabs/knp-paginator-bundle . It already has more features and easier to use than Doctrine pagination. Doctrine pagination is kinda low-level thing and you need to do more work to get things work with it, so KnpPaginatorBundle is easier to use and more powerful - you can think about it as a wrapper around native Doctrine pagination. But in case you don't want to install more bundles in your project - yes, you can take a look at that Doctrine pagination, it already provides with Doctrine library, their docs are pretty good on it, so you can easily start with it, though still will need to do more work to get things work as they works in KnpPaginatorBundle.

That's why this tutorial is not out of date and still relevant :)

Cheers!

1 Reply
Oliver-W Avatar
Oliver-W Avatar Oliver-W | posted 2 years ago

Hi,

for me the templates do not work. I see the pagination links at the bottom but no matter which template I choose in knp_paginator.yaml: no changes on the output.
Any hints for me??
Thx

Reply
Oliver-W Avatar

Aaaargh, it was the cache. Clearing the cache and it works.

Reply

hehehe it happens!

Reply
Carlos M. Avatar
Carlos M. Avatar Carlos M. | posted 3 years ago

I was wondering if anyone has figured out a simple way of getting the pagination autocomplete work for both the pagination and getItems() being interpreted as an array of the objects being paginated in a Twig template.

At the moment, what I'm doing is this:

$pagination = $paginator->paginate($replacementAdsQuery, $page, self::ADS_PER_PAGE); // Which is a Pagination\PaginationInterface object

/* @var $replacementAds ReplacementAd[] */
$replacementAds = $pagination->getItems();

And then I pass both variables to the Twig template:

return $this->render('public/replacement_ads/list.html.twig', [
'pagination' => $pagination,
'replacementAds' => $replacementAds,
]);

If you only pass the PaginationInterface to the Twig template, you'd only be able to get PhpStorm (other IDEs probably too) autocomplete working for the PaginationInterface object, but you won't get the autocomplete for the $replacementAds array (or whatever array of object you are paginating).

Any room for improving / simplifying this code?

P.S. If you copy some text in the textarea of the comments box, it just ads several lines/break lines in each paragraph of your comment. Making it REALLY hard to copy paste anything (like code snippets). This is at least happening in Firefox (latest) in Linux.

Reply

Hey Carlos,

Hm, nothing much we can do here probably, especially if you want to autocomplete getItems() for different entities. Probably your solution is the best you can do. The next improvement would be to write a plugin for PhpStorm (or add support of KnpPaginatorBundle to Symfony Plugin) that will help with autocomplete. But that's complex and really the next level. So, I'd recommend you to stick to your workaround, or if you really curious about it - try to figure out how to write your custom PhpStorm plugin for this.

> P.S. If you copy some text in the textarea of the comments box, it just ads several lines/break lines in each paragraph of your comment. Making it REALLY hard to copy paste anything (like code snippets). This is at least happening in Firefox (latest) in Linux.

Yeah, I also noticed this problem, that's so annoying, not sure when Disqus will fix it for Firefox. As a workaround for this - use Google Chrome, it works great for copy/pasting :)

I hope this helps!

Cheers!

Reply
Carlos M. Avatar

Thanks for the quick reply :) That's what I thought, but wanted to reassure I wasn't overkilling or making things hard.

Most of the times, I implement the twig template without pagination at first, so it doesn't bother much. But when you come back to that template and try to add more fields or display more information from the paginated objects, it becomes annoying.

Will keep with my way then. Cheers!

Reply

Hey Carlos,

Yeah, I see, it would be cool to have full autocompletion, but unfortunately. Though your workaround is good and simple I think :) Thanks for sharing it with others!

Cheers!

Reply
Lothar D. Avatar
Lothar D. Avatar Lothar D. | posted 3 years ago

Hi, I want to know how did the terminal search box come out?

Reply

Hey Lothar,

Good question! Usually, if you're on a Mac, it works by pressing Cmd + F - it's a general shortcut for search. Same should work in your browser. On Windows/Linux try Ctrl + F.

And you always can find how to search in app menu, like in your terminal app menu. For me it's "Edit" -> "Find" -> "Find..." item in the menu, and it also shows its shortcut there.

I hope this helps!

Cheers!

Reply
Robert Avatar

Hey i get this error when trying to show the pages

''There are no registered paths for namespace "KnpPaginator".'

Anyone else having this Problem ?

Reply

Hey Robert

Is it possible that you forgot to register the bundle? If you are on Symfony4+ it should be done automatically


// app/AppKernel.php
public function registerBundles()
{
    return [
        // ...
        new Knp\Bundle\PaginatorBundle\KnpPaginatorBundle(),
        // ...
    ];
}
Reply
Robert Avatar

Hey the Bundle is registered as so Knp\Bundle\PaginatorBundle\KnpPaginatorBundle::class => ['all' => true],

Reply

Hey Roby,

Hm, could you clear the cache and try again? If you still have this issue - most probably the problem with versions. Please, take a look at this thread, you will find a workaround at the end that should help: https://github.com/KnpLabs/...

Cheers!

Reply
Edward B. Avatar
Edward B. Avatar Edward B. | posted 3 years ago

For Symfony 5, use the Doctrine Paginator https://www.doctrine-projec... - Fabien's book (section 10.6 Paginating the Comments) has the example which matches this chapter fairly closely.

Reply

Hey Edward B.!

I like the Doctrine paginator too - it's something powerful and relatively easy that you get out-of-the-box. I *do* wish that I could pass it the "max per page" and "page number" instead of needing to calculate the limit & the offset - that would give me the extra "developer experience" that I crave with it. But that's ultimately a minor detail - I also used it recently for something.

Cheers!

Reply
Edward B. Avatar

That brings out a question - suppose I created a wrapper for the Doctrine paginator which DOES translate limit/offset because it's something I do a lot. Where would you put it? I suppose if there were many such wrappers (three or more), put them in a separate folder; but until then put them in the Repository folder since they're query-related?

Reply

Hey Ed,

You might be interested to look at implementation of Paginator in Symfony Demo that is based on Doctrine paginator :)

https://github.com/symfony/...

Cheers!

Reply

Yea, that's a good question. My instinct is that I'd create a PaginatorFactory class. We would pass it the QueryBuilder and then it would set the limit/offset stuff. Something like:


// PaginatorFactory

public function createPaginator(QueryBuilder $queryBuilder, int $currentPage, int $maxPerPage = 10): Paginator
{
    $offset = ($currentPage - 1) * $maxPerPage;

    $queryBuilder
        ->setMaxResults($maxPerPage)
        ->setFirstResult($offset);

    return new Paginator($queryBuilder);
}

For the QueryBuilder itself, that should live in some repository. So the code that uses this would look something like:


$paginator = $paginatorFactory->createPaginator(
    $someRepository->createAlphabticalQueryBuilder(),
    $request->query->get('page')
);

There are certainly other valid ways of doing it - but that's probably what I would do :).

Cheers!

Reply
Balázs S. Avatar
Balázs S. Avatar Balázs S. | posted 3 years ago

Oh, boy! Watch out for using PaginatorInterface and not PaginationInterface! :D Took me awhile to figure it out... (sigh). I knew something was fishy when Storm said there's no paginate() method...

Reply

Hey Payskin,

Nice tip! :) Yep, you always should look closer to the namespaces you use in PhpStorm, some of them very similar and better to double check you use the correct namespace because it's easy to mess things up on this stage.

Cheers!

Reply
Cat in space

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

The course is built on Symfony 4, but the principles still apply perfectly to Symfony 5 - not a lot has changed in the world of relations!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "knplabs/knp-paginator-bundle": "^2.7", // v2.7.2
        "knplabs/knp-time-bundle": "^1.8", // 1.8.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.1.4
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.0.4
        "symfony/console": "^4.0", // v4.0.14
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/framework-bundle": "^4.0", // v4.0.14
        "symfony/lts": "^4@dev", // dev-master
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/twig-bundle": "^4.0", // v4.0.4
        "symfony/web-server-bundle": "^4.0", // v4.0.4
        "symfony/yaml": "^4.0", // v4.0.14
        "twig/extensions": "^1.5" // v1.5.1
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.4
        "fzaninotto/faker": "^1.7", // v1.7.1
        "symfony/debug-bundle": "^3.3|^4.0", // v4.0.4
        "symfony/dotenv": "^4.0", // v4.0.14
        "symfony/maker-bundle": "^1.0", // v1.4.0
        "symfony/monolog-bundle": "^3.0", // v3.1.2
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.0.4
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.0.4
    }
}
userVoice