If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeOn 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.
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!
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.
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.
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!
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
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!
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)
.......
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!
Hi
I'm trying to tidy up the url so that ?page=3 becomes /3/, is there anyway to do this?
Cheers
Steve
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.
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
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!
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
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
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.
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!
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.
Oh, yes, whenever you create a new translation file you have to clear the cache. Thanks for sharing the steps!
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.
"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.
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!
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 ?
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!
Hey @Farry7
What exactly not working? styling or HTML markup? do you have any errors in console?
Cheers!
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.
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!
And what about https://www.doctrine-projec...
I think this tutorial have to be updated with provided reference because it out of date.
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!
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
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.
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!
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!
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!
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!
Hey i get this error when trying to show the pages
''There are no registered paths for namespace "KnpPaginator".'
Anyone else having this Problem ?
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(),
// ...
];
}
Hey the Bundle is registered as so Knp\Bundle\PaginatorBundle\KnpPaginatorBundle::class => ['all' => true],
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!
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.
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!
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?
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!
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!
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...
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!
// 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
}
}
I had a question but it figured itself out.
Anyways, thanks for the great tutorials!!