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 SubscribeSince pagination always looks the same, no matter what you're listing, I really want to organize my code so that pagination is effortless in the future. This took way too many lines of code.
Inside of the Pagination/
directory, create a new PHP class called PaginationFactory
. There, add a new public function createCollection()
method: this will create the entire final PaginatedCollection
object for some collection resource. To do this, we'll need to pass it a few things, starting with the $qb
and the $request
- we'll use that to find the current page. The method will also need to know the route for the links and any $routeParams
it needs:
... lines 1 - 2 | |
namespace AppBundle\Pagination; | |
use Doctrine\ORM\QueryBuilder; | |
... lines 6 - 7 | |
use Symfony\Component\HttpFoundation\Request; | |
... lines 9 - 10 | |
class PaginationFactory | |
{ | |
... lines 13 - 19 | |
public function createCollection(QueryBuilder $qb, Request $request, $route, array $routeParams = array()) | |
{ | |
... lines 22 - 53 | |
} | |
} |
Go back to ProgrammerController
, copy the logic, remove it and put it into PaginationFactory
. Add the missing use
statements: by auto-completing the classes DoctrineORMAdapter
and Pagerfanta
. Now, delete $route
and $routeParams
since those are passed as arguments. Remove the $qb
variable for the same reason:
... lines 1 - 5 | |
use Pagerfanta\Adapter\DoctrineORMAdapter; | |
use Pagerfanta\Pagerfanta; | |
... lines 8 - 10 | |
class PaginationFactory | |
{ | |
... lines 13 - 19 | |
public function createCollection(QueryBuilder $qb, Request $request, $route, array $routeParams = array()) | |
{ | |
$page = $request->query->get('page', 1); | |
$adapter = new DoctrineORMAdapter($qb); | |
$pagerfanta = new Pagerfanta($adapter); | |
$pagerfanta->setMaxPerPage(10); | |
$pagerfanta->setCurrentPage($page); | |
$programmers = []; | |
foreach ($pagerfanta->getCurrentPageResults() as $result) { | |
$programmers[] = $result; | |
} | |
$paginatedCollection = new PaginatedCollection($programmers, $pagerfanta->getNbResults()); | |
$createLinkUrl = function($targetPage) use ($route, $routeParams) { | |
return $this->router->generate($route, array_merge( | |
$routeParams, | |
array('page' => $targetPage) | |
)); | |
}; | |
$paginatedCollection->addLink('self', $createLinkUrl($page)); | |
$paginatedCollection->addLink('first', $createLinkUrl(1)); | |
$paginatedCollection->addLink('last', $createLinkUrl($pagerfanta->getNbPages())); | |
if ($pagerfanta->hasNextPage()) { | |
$paginatedCollection->addLink('next', $createLinkUrl($pagerfanta->getNextPage())); | |
} | |
if ($pagerfanta->hasPreviousPage()) { | |
$paginatedCollection->addLink('prev', $createLinkUrl($pagerfanta->getPreviousPage())); | |
} | |
return $paginatedCollection; | |
} | |
} |
In fact, move that back to ProgrammerController
: we'll want it in a minute:
... lines 1 - 18 | |
class ProgrammerController extends BaseController | |
{ | |
... lines 21 - 76 | |
public function listAction(Request $request) | |
{ | |
$qb = $this->getDoctrine() | |
->getRepository('AppBundle:Programmer') | |
->findAllQueryBuilder(); | |
... lines 82 - 84 | |
$response = $this->createApiResponse($paginatedCollection, 200); | |
return $response; | |
} | |
... lines 89 - 187 | |
} |
The only other problem here is $this->generateUrl()
: that method does not exist outside of the controller. That's ok: since we do need to generate URLs, this just means we need the router
. Add a __construct()
function at the top with RouterInterface
as an argument. I'll use the Alt
+ enter
PHPStorm shortcut to create and set that property:
... lines 1 - 8 | |
use Symfony\Component\Routing\RouterInterface; | |
class PaginationFactory | |
{ | |
private $router; | |
public function __construct(RouterInterface $router) | |
{ | |
$this->router = $router; | |
} | |
... lines 19 - 54 | |
} |
Back inside createCollection()
, change $this->generateUrl()
to $this->router->generate()
:
... lines 1 - 10 | |
class PaginationFactory | |
{ | |
... lines 13 - 19 | |
public function createCollection(QueryBuilder $qb, Request $request, $route, array $routeParams = array()) | |
{ | |
... lines 22 - 35 | |
$createLinkUrl = function($targetPage) use ($route, $routeParams) { | |
return $this->router->generate($route, array_merge( | |
$routeParams, | |
array('page' => $targetPage) | |
)); | |
}; | |
... lines 42 - 53 | |
} | |
} |
Our work in this class is done! Next, register this as service in app/config/services.yml
- let's call it pagination_factory
. How creative! Set the class to PaginationFactory
and pass one key for arguments
: @router
:
... lines 1 - 5 | |
services: | |
... lines 7 - 25 | |
pagination_factory: | |
class: AppBundle\Pagination\PaginationFactory | |
arguments: ['@router'] |
Tip
If you're using Symfony 3.3, your app/config/services.yml
contains some extra code
that may break things when following this tutorial! To keep things working - and learn
about what this code does - see https://knpuniversity.com/symfony-3.3-changes
Copy the service name and open ProgrammerController
to hook this all up. Now, just use $paginatedCollection =
$this->get('pagination_factory')->createCollection() and pass it the 4 arguments: $qb
, $request
, the route name - api_programmers_collection
- and the route params:
... lines 1 - 18 | |
class ProgrammerController extends BaseController | |
{ | |
... lines 21 - 76 | |
public function listAction(Request $request) | |
{ | |
... lines 79 - 81 | |
$paginatedCollection = $this->get('pagination_factory') | |
->createCollection($qb, $request, 'api_programmers_collection'); | |
... lines 84 - 87 | |
} | |
... lines 89 - 187 | |
} |
Actually, most of the time you won't have route params. So head back into PaginationFactory
and make that argument optional:
... lines 1 - 10 | |
class PaginationFactory | |
{ | |
... lines 13 - 19 | |
public function createCollection(QueryBuilder $qb, Request $request, $route, array $routeParams = array()) | |
{ | |
... lines 22 - 53 | |
} | |
} |
Much better.
Now, PhpStorm should be happy... but it's still not! It looks more like someone stole it's ice cream. Ah, I forgot to return $paginatedCollection
in PaginationFactory
:
... lines 1 - 10 | |
class PaginationFactory | |
{ | |
... lines 13 - 19 | |
public function createCollection(QueryBuilder $qb, Request $request, $route, array $routeParams = array()) | |
{ | |
... lines 22 - 52 | |
return $paginatedCollection; | |
} | |
} |
PhpStorm was complaining that createCollection()
didn't look like it returned anything... and it was right! The robots are definitely taking over.
Run the test to see if we broke anything:
./bin/phpunit -c app --filter filterGETProgrammersCollectionPaginated
We didn't! What a delightful surprise.
Now, if you want some sweet pagination, just create a QueryBuilder
, pass it into the PaginationFactory
, pass that to createApiResponse
and then go find some ice cream.
This is like 20th lesson or so I listened that was recorded by you. Must say, I enjoy every second of those. Very knowledgeable, funny, fast... just keep doing what you're doing :)
Hello Ryan,
In my project I have a back office to create sites and their content pars example images.
These images are sent to the server vai a form and I put them in a folder:
web / uplaod / siteName / iamges /.
knowing that these images are on a server 1 and I want to create a cron to move them later on another server 2.
can I create a cron myself by exploiting the power of symfony?
Hey Nizar
That's totally posible to do in Symfony (except the part of registering a CRON job into your server). You can create a Symfony command that actually move those files, and then, set up a CRON job that executes that command whenever you need it.
You can read more info about creating a Symfony command here: https://symfony.com/doc/cur...
Cheers!
Hi Ryan,
It's really nothing, but for consistency, shouldn't you rename "programmers" variable into "items" inside createCollection method ?
Hi,
I want to use a pagintion in my project but I hesitate between : KnpPaginatorBundle and WhiteOctoberPagerfantaBundle
you advise me which one.
thanks for your replay
Hi Nizar!
They're both good - at this time, I'd slightly recommend Knp, but they are both maintained quite well :).
Cheers!
Hi ryan,
I have in my project a document entity that has a ManytoOne type relationship with another image entity. So I have to create an interface to send images to the server for the document entity
and I would like to use a bundle that contains the DropZone plugin for example.
I develop this feature myself? or I use a bundle?
if not, do you recommend which bundle?
thanks for the answer.
Hey Ali,
If there's a good bundle - you better to use it instead of re-doing things manually. Third-party bundles have some advantages like you don't need to spend your time for writing this feature, good bundles are well tested, etc. I personally do not use any bundles that has integration with DropZone, but after google it quickly I found a one, so take a look at: https://github.com/1up-lab/... that is pretty popular on GitHub and have a lot of integration with other JS uploaders.
Cheers!
// composer.json
{
"require": {
"php": ">=5.3.3",
"symfony/symfony": "2.6.*", // v2.6.11
"doctrine/orm": "~2.2,>=2.2.3,<2.5", // v2.4.7
"doctrine/dbal": "<2.5", // v2.4.4
"doctrine/doctrine-bundle": "~1.2", // v1.4.0
"twig/extensions": "~1.0", // v1.2.0
"symfony/assetic-bundle": "~2.3", // v2.6.1
"symfony/swiftmailer-bundle": "~2.3", // v2.3.8
"symfony/monolog-bundle": "~2.4", // v2.7.1
"sensio/distribution-bundle": "~3.0,>=3.0.12", // v3.0.21
"sensio/framework-extra-bundle": "~3.0,>=3.0.2", // v3.0.7
"incenteev/composer-parameter-handler": "~2.0", // v2.1.0
"hautelook/alice-bundle": "0.2.*", // 0.2
"jms/serializer-bundle": "0.13.*", // 0.13.0
"white-october/pagerfanta-bundle": "^1.0" // v1.2.4
},
"require-dev": {
"sensio/generator-bundle": "~2.3", // v2.5.3
"behat/behat": "~3.0", // v3.0.15
"behat/mink-extension": "~2.0.1", // v2.0.1
"behat/mink-goutte-driver": "~1.1.0", // v1.1.0
"behat/mink-selenium2-driver": "~1.2.0", // v1.2.0
"phpunit/phpunit": "~4.6.0" // 4.6.4
}
}
Hi,
In my case, i applied this <strong>rawurldecode</strong> function to fix query params encoding:
Before rawurldecode:
After rawurldecode:
=)