Pagination
- class
Cake\Controller\Component\
PaginatorComponent
- One of the main obstacles of creating flexible and user-friendly webapplications is designing an intuitive user interface. Many applications tend togrow in size and complexity quickly, and designers and programmers alike findthey are unable to cope with displaying hundreds or thousands of records.Refactoring takes time, and performance and user satisfaction can suffer.
Displaying a reasonable number of records per page has always been a criticalpart of every application and used to cause many headaches for developers.CakePHP eases the burden on the developer by providing a quick, easy way topaginate data.
Pagination in CakePHP is offered by a component in the controller, to makebuilding paginated queries easier. In the ViewView\Helper\PaginatorHelper
is used to make the generationof pagination links & buttons simple.
Using Controller::paginate()
In the controller, we start by defining the default query conditions paginationwill use in the $paginate
controller variable. These conditions, serve asthe basis for your pagination queries. They are augmented by the sort
, direction
,limit
, and page
parameters passed in from the URL. It is important to notethat the order
key must be defined in an array structure like below:
- class ArticlesController extends AppController
- {
- public $paginate = [
- 'limit' => 25,
- 'order' => [
- 'Articles.title' => 'asc'
- ]
- ];
- public function initialize()
- {
- parent::initialize();
- $this->loadComponent('Paginator');
- }
- }
You can also include any of the options supported byORM\Table::find()
, such as fields
:
- class ArticlesController extends AppController
- {
- public $paginate = [
- 'fields' => ['Articles.id', 'Articles.created'],
- 'limit' => 25,
- 'order' => [
- 'Articles.title' => 'asc'
- ]
- ];
- public function initialize()
- {
- parent::initialize();
- $this->loadComponent('Paginator');
- }
- }
While you can pass most of the query options from the paginate property it isoften cleaner and simpler to bundle up your pagination options intoa Custom Finder Methods. You can define the finder pagination uses bysetting the finder
option:
- class ArticlesController extends AppController
- {
- public $paginate = [
- 'finder' => 'published',
- ];
- }
Because custom finder methods can also take in options, this is how you pass inoptions into a custom finder method within the paginate property:
- class ArticlesController extends AppController
- {
- // find articles by tag
- public function tags()
- {
- $tags = $this->request->getParam('pass');
- $customFinderOptions = [
- 'tags' => $tags
- ];
- // the custom finder method is called findTagged inside ArticlesTable.php
- // it should look like this:
- // public function findTagged(Query $query, array $options) {
- // hence you use tagged as the key
- $this->paginate = [
- 'finder' => [
- 'tagged' => $customFinderOptions
- ]
- ];
- $articles = $this->paginate($this->Articles);
- $this->set(compact('articles', 'tags'));
- }
- }
In addition to defining general pagination values, you can define more than oneset of pagination defaults in the controller, you just name the keys of thearray after the model you wish to configure:
- class ArticlesController extends AppController
- {
- public $paginate = [
- 'Articles' => [],
- 'Authors' => [],
- ];
- }
The values of the Articles
and Authors
keys could contain all the propertiesthat a model/key less $paginate
array could.
Once the $paginate
property has been defined, we can use theController\Controller::paginate()
method to create thepagination data, and add the PaginatorHelper
if it hasn’t already beenadded. The controller’s paginate method will return the result set of thepaginated query, and set pagination metadata to the request. You can access thepagination metadata at $this->request->getParam('paging')
. A more completeexample of using paginate()
would be:
- class ArticlesController extends AppController
- {
- public function index()
- {
- $this->set('articles', $this->paginate());
- }
- }
By default the paginate()
method will use the default model fora controller. You can also pass the resulting query of a find method:
- public function index()
- {
- $query = $this->Articles->find('popular')->where(['author_id' => 1]);
- $this->set('articles', $this->paginate($query));
- }
If you want to paginate a different model you can provide a query for it, thetable object itself, or its name:
- // Using a query
- $comments = $this->paginate($commentsTable->find());
- // Using the model name.
- $comments = $this->paginate('Comments');
- // Using a table object.
- $comments = $this->paginate($commentTable);
Using the Paginator Directly
If you need to paginate data from another component you may want to use thePaginatorComponent directly. It features a similar API to the controllermethod:
- $articles = $this->Paginator->paginate($articleTable->find(), $config);
- // Or
- $articles = $this->Paginator->paginate($articleTable, $config);
The first parameter should be the query object from a find on table object youwish to paginate results from. Optionally, you can pass the table object and letthe query be constructed for you. The second parameter should be the array ofsettings to use for pagination. This array should have the same structure as the$paginate
property on a controller. When paginating a Query
object, thefinder
option will be ignored. It is assumed that you are passing inthe query you want paginated.
Paginating Multiple Queries
You can paginate multiple models in a single controller action, using thescope
option both in the controller’s $paginate
property and in thecall to the paginate()
method:
- // Paginate property
- public $paginate = [
- 'Articles' => ['scope' => 'article'],
- 'Tags' => ['scope' => 'tag']
- ];
- // In a controller action
- $articles = $this->paginate($this->Articles, ['scope' => 'article']);
- $tags = $this->paginate($this->Tags, ['scope' => 'tag']);
- $this->set(compact('articles', 'tags'));
The scope
option will result in PaginatorComponent
looking inscoped query string parameters. For example, the following URL could be used topaginate both tags and articles at the same time:
- /dashboard?article[page]=1&tag[page]=3
See the Paginating Multiple Results section for how to generate scoped HTMLelements and URLs for pagination.
New in version 3.3.0: Multiple Pagination was added in 3.3.0
Paginating the Same Model multiple Times
To paginate the same model multiple times within a single controller action youneed to define an alias for the model. See Using the TableRegistry foradditional details on how to use the table registry:
- // In a controller action
- $this->paginate = [
- 'ArticlesTable' => [
- 'scope' => 'published_articles',
- 'limit' => 10,
- 'order' => [
- 'id' => 'desc',
- ],
- ],
- 'UnpublishedArticlesTable' => [
- 'scope' => 'unpublished_articles',
- 'limit' => 10,
- 'order' => [
- 'id' => 'desc',
- ],
- ],
- ];
- // Register an additional table object to allow differentiating in pagination component
- TableRegistry::config('UnpublishedArticles', [
- 'className' => 'App\Model\Table\ArticlesTable',
- 'table' => 'articles',
- 'entityClass' => 'App\Model\Entity\Article',
- ]);
- $publishedArticles = $this->paginate(
- $this->Articles->find('all', [
- 'scope' => 'published_articles'
- ])->where(['published' => true])
- );
- $unpublishedArticles = $this->paginate(
- TableRegistry::getTableLocator()->get('UnpublishedArticles')->find('all', [
- 'scope' => 'unpublished_articles'
- ])->where(['published' => false])
- );
Control which Fields Used for Ordering
By default sorting can be done on any non-virtual column a table has. This issometimes undesirable as it allows users to sort on un-indexed columns that canbe expensive to order by. You can set the whitelist of fields that can be sortedusing the sortWhitelist
option. This option is required when you want tosort on any associated data, or computed fields that may be part of yourpagination query:
- public $paginate = [
- 'sortWhitelist' => [
- 'id', 'title', 'Users.username', 'created'
- ]
- ];
Any requests that attempt to sort on fields not in the whitelist will beignored.
Limit the Maximum Number of Rows per Page
The number of results that are fetched per page is exposed to the user as thelimit
parameter. It is generally undesirable to allow users to fetch allrows in a paginated set. The maxLimit
option asserts that no one can setthis limit too high from the outside. By default CakePHP limits the maximumnumber of rows that can be fetched to 100. If this default is not appropriatefor your application, you can adjust it as part of the pagination options, forexample reducing it to 10
:
- public $paginate = [
- // Other keys here.
- 'maxLimit' => 10
- ];
If the request’s limit param is greater than this value, it will be reduced tothe maxLimit
value.
Joining Additional Associations
Additional associations can be loaded to the paginated table by using thecontain
parameter:
- public function index()
- {
- $this->paginate = [
- 'contain' => ['Authors', 'Comments']
- ];
- $this->set('articles', $this->paginate($this->Articles));
- }
Out of Range Page Requests
The PaginatorComponent will throw a NotFoundException
when trying toaccess a non-existent page, i.e. page number requested is greater than totalpage count.
So you could either let the normal error page be rendered or use a try catchblock and take appropriate action when a NotFoundException
is caught:
- // Prior to 3.6 use Cake\Network\Exception\NotFoundException
- use Cake\Http\Exception\NotFoundException;
- public function index()
- {
- try {
- $this->paginate();
- } catch (NotFoundException $e) {
- // Do something here like redirecting to first or last page.
- // $this->request->getParam('paging') will give you required info.
- }
- }
Pagination in the View
Check the View\Helper\PaginatorHelper
documentation forhow to create links for pagination navigation.