Buy
Buy

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

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!

  • 2018-11-14 Victor Bocharsky

    Hey Fernando,

    Haha, I see your point :) Well, using third party paginator has some advantages. First of all, you'll save your time, you don't need to implement it, just install and use. I think you're agree with me that implementing even a simple version of paginator requires more time then installing/configure it :) Also, popular third-party code is usually well-tested, so you don't need to write tests for this functionality as it's done by maintainers - once again you save some time. And if you have a few projects where you need pagination, you'll probably want to share your custom paginator between these projects, and it also require some time... so I think eventually you'll come up to a third party paginator or create another paginator bundle, your own... but the question why? :) Anyway, it depends on your case, probably you just need a pretty straightforward and simple solution and using third-party paginator is just overkill? So, it depends, but that's what my opinion on it :)

    Cheers!

  • 2018-11-14 Fernando Andrade

    is it just me or this is a complete waste? I mean, cool that exists but this is something that one can code instead of importing yet another dependency...

    just my opinion, don't get offended lol :P

  • 2018-07-24 cybernet2u

    fix it :) my fault
    "a.author" was on another mysql table, therefore i had to use join :)

  • 2018-07-24 cybernet2u

    can you also check this ? https://stackoverflow.com/q...
    it may be related to count() from paginator ...

  • 2018-07-23 cybernet2u

    public function getWithSearchQueryBuilder(?string $term): QueryBuilder
    {
    $qb = $this->createQueryBuilder('a');
    if ($term) {
    $qb->andWhere('a.content LIKE :term OR a.author LIKE :term OR a.title LIKE :term')
    ->setParameter('term', '%' . $term . '%');
    }
    return $qb->orderBy('a.createdAt', 'DESC');
    }


    public function Home(EntityManagerInterface $em, PaginatorInterface $paginator, Request $request)
    {
    $q = $request->query->get('q');
    $repository = $em->getRepository(Advert::class);
    $queryBuilder = $repository->getWithSearchQueryBuilder($q);
    $pagination = $paginator->paginate(
    $queryBuilder,
    $request->query->getInt('page', 1),
    10
    );

    return $this->render('home.html.twig', [
    'pagination' => $pagination,
    ]);
    }
  • 2018-07-23 weaverryan

    Hey cybernet2u!

    Hmmm. I'm not aware of anything that would have changed recently that could cause this issue! Can you post your getWithSearchQueryBuilder() method from CommentRepository - and also your controller code?

    Cheers!

  • 2018-07-22 cybernet2u

    symfony 4.1.1

    i get [Semantical Error] line 0, col 66 near 'author LIKE :term': Error: Invalid PathExpression. Must be a StateFieldPathExpression.

    only when i make a search

    any ideea ?

  • 2018-07-06 Victor Bocharsky

    Hey Nicolas,

    Ah, that was the key. Thanks for confirming that "Knp\Component\Pager\PaginatorInterface" fixes the problem.

    Cheers!

  • 2018-07-05 Nicolas Petitjean

    It works, thanks, i was using "Knp\Component\Pager\Pagination\PaginationInterface;"

  • 2018-07-02 Victor Bocharsky

    Hey Nicolas,

    Did you try to type hint this argument with "Knp\Component\Pager\PaginatorInterface" interface?


    use Knp\Component\Pager\PaginatorInterface;

    class CommentAdminController extends Controller
    {
    public function index(CommentRepository $repository, Request $request, PaginatorInterface $paginator)
    {
    // ...
    }
    }

    Please, make sure you have "use Knp\Component\Pager\PaginatorInterface;" used namespace in the beginning, looks like you just typehinted with incorrect namespace. And btw, you don't need that "$pagination: '@knp_paginator'" at all, just try the namespace I mentioned.

    If it still does not work, what version of Kn[PaginatorBundle do you use?

    Cheers!

  • 2018-06-30 Nicolas Petitjean

    Hello

    Got this error :

    Controller "App\Controller\CommentAdminController::index()" requires that you provide a value for the "$pagination" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.

    The only way to get this working is to bind a var in services.yaml :

    $pagination: '@knp_paginator'

    But this bring me this error :

    Type error: Argument
    3 passed to App\Controller\CommentAdminController::index() must implement interface Knp\Component\Pager\Pagination\PaginationInterface, instance of Knp\Component\Pager\Paginator given

    So i have to remove the type hint in the method :

    public function index(CommentRepository $repository, Request $request, $pagination) {
    ...
    }

    symfony 4.0.4

  • 2018-06-28 Daniel

    Hey that's awesome! Thanks, Diego!

  • 2018-06-28 Diego Aguiar

    Hey Daniel

    Using the : return_type syntax is the new way for specifing return types in PHP7, and it will throw an exception if your method return anything else than stated, but if you only specify the return type in a PHPdoc block, then nothing will check the actual return type, it's only for helping IDE's and for documentation.

    Cheers!

  • 2018-06-28 Daniel

    Hi!

    In this code:

    /**
    * @param null|string $term
    */
    public function findAllWithSearch(?string $term): QueryBuilder

    What's the difference between declaring the return type after ":" or at * @return QueryBuilder?

    Thanks!

  • 2018-06-22 Diego Aguiar

    Hey Mouad Errahmouni

    I'm afraid we do not count with any bundle for that topic, but, I just found one that looks good, probably you could give it a try: https://github.com/stwe/Dat...

    Cheers!

  • 2018-06-22 Mouad Errahmouni

    Hello guys,
    Am wondering if there is a knpbundle for datatable, if yes, is there a course for it?
    Thanks in Advance

  • 2018-06-16 weaverryan

    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.

  • 2018-06-15 Diego Aguiar

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

  • 2018-06-15 Peter Kosak

    For beginners like me:
    run "composer require symfony/translation"
    then create file "translations/KnpPaginatorBundle.en.yaml"
    paste there

    label_next: Next
    label_previous: Previous

    Then I had to run "php bin/console cache:clear"

    That seems to do the trick and everything is sorted.

  • 2018-06-13 Diego Aguiar

    Hey Peter Kosak

    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!

  • 2018-06-13 Peter Kosak

    All is working except my pagination labels for previous & next are : label_previous & label_next also on profiler bar I have 2 translation errors:

    en KnpPaginatorBundle 1 label_previous label_previous
    en KnpPaginatorBundle 1 label_next label_next

    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.

  • 2018-06-01 Victor Bocharsky

    Hey Alexandrer,

    Yeah, it's weird. I suppose you're talking about PHP warning, not a fatal error. It's difficult to say, you need to see the backtrace to understand where this error comes from. It sounds like a bug in somewhere, and it would be better to fix to be sure everything works well.

    Cheers!

  • 2018-05-31 Alexander Enlund

    It's really weird... it works but the error remains, yes I did use:
    {{ pagination.getTotalItemCount }} and that's where the error is...
    ...but I guess if it works then there is nothing to worry about, right.
    (thanks for the quick reply!)

  • 2018-05-31 Victor Bocharsky

    Hey Alexander,

    Do you see this error due to this "{{ pagination.getTotalItemCount }}" code? This is a valid syntax - I see it from the docs: https://github.com/KnpLabs/... . Did you call $paginator->paginate(...) method in your controller with proper arguments to initialize pagination? See example in docs: https://github.com/KnpLabs/... . If so, could you dump your "pagination" object in Twig? What object is it?

    Cheers!

  • 2018-05-31 Alexander Enlund

    Hey! Just one question, how come you can use methods and such even though it says: "field or method not found under construction." ? (e.g. {{ pagination.getTotalItemCount }})