So I've been working on a pagination system that pulls data externally, after finally figuring out through trial and error I got the solution that works. While going through it something struck me as odd, as per the documentation $paginator = Paginator::make($items, $totalItems, $perPage);
I was wondering what's the actual use of the $perPage parameter? You would think that what ever number is specified would show that many items. But with manual pagination you have to limit the results that are passed into $items in order for it to work, otherwise you get the output of all items (as shown in code block below). Is manual pagination flawed? because if $perPage doesn't match the total number of items in the array $items it shows everything.
Example: Paginator::make( array('10xarray') ), 10, 2); it would show 5 pages with 2 items per page? where in reality it actually shows 10 items with 5 pages that all show the same 10 items.
<?php
class MainController extends BaseController {
public function library()
{
$this->layout->title = 'testing';
$this->layout->main = View::make('library/layout');
// Pagination data
$media = array(
array('title' => 'test'),
array('title' => 'test'),
array('title' => 'test'),
array('title' => 'test')
);
$perPage = 2;
$currentPage = Input::get('page', 1);
$pagedData = array_slice($media, ($currentPage - 1) * $perPage, $perPage);
$this->layout->main->paginated = Paginator::make($pagedData, count($media), $perPage);
if(Request::ajax()) {
return Response::json(
View::make(
'library/layout',
array('paginated' => $this->layout->main->paginated)
)->render()
);
}
}
}
it isn't flawed. it is intended behavior.
this manual paginator is just a container. nothing more, nothing less. how you implement it, is upon you.
without a LIMIT query, you can never do a pagination. when you use pagination(), laravel does the work under the hood. when you go for manual, you have to do it manually and get greater control.
why do you think it is called manual pagination in the first place?
Related
I have a table that contains different versions of a text. I want to display the diffs of each version with the previous version. I also want to paginate through the versions, in case there are more than 20. However, to diff the last text on each page I would need the first text of the next page. I cannot just make the page size one larger (21 in this case), because the second page would skip its first entity, and the third its first two etc.
$config = $this->Paginator->getConfig();
$this->Paginator->setConfig('limit', $config['limit'] + 1);
$inscriptions = $this->paginate($query);
I might instead be able to solve the problem by making a separate ->paginate() call for the single entity, but I would rather not execute a second query if possible.
$inscriptions = $this->paginate($query);
$config = $this->Paginator->getConfig();
$this->Paginator->setConfig([
'limit' => 1,
'page' => ($config['page'] * $config['limit']) + 1
]);
$inscriptions[] = $this->paginate($query)->first();
Is there a way to skip the first n results? In that case I could set the page size to 21 but set the page number to 1, and skip the first ((old page number - 1) * old page size) entities.
It is possible to make a custom paginator that extends the default one, but functions as described:
<?php
namespace App\Datasource;
use Cake\Core\InstanceConfigTrait;
use Cake\Datasource\Paginator;
use Cake\Datasource\QueryInterface;
use Cake\Datasource\RepositoryInterface;
class DiffPaginator extends Paginator
{
use InstanceConfigTrait;
protected function getQuery(RepositoryInterface $object, ?QueryInterface $query = null, array $data): QueryInterface
{
$data['options']['offset'] = ($data['options']['page'] - 1) * $data['options']['limit'];
$data['options']['limit'] += 1;
unset($data['options']['page']);
return parent::getQuery($object, $query, $data);
}
protected function buildParams(array $data): array
{
$paging = parent::buildParams($data);
if ($paging['current'] == $paging['perPage'] + 1) {
$paging['current'] -= 1;
}
return $paging;
}
}
In your controller then use the following:
$this->Paginator->setPaginator(new DiffPaginator);
CakePHP 3.7.7
I'm using the Paginator (https://book.cakephp.org/3.0/en/views/helpers/paginator.html) to display pagination for a query that has 972 rows.
In my Controller method I am loading the Paginator component and using a custom finder called findFilters() to return the data that I want paginated.
public function initialize()
{
parent::initialize();
$this->loadComponent('Paginator');
}
public $paginate = [
'finder' => 'filters', // uses custom finder, findFilters() in src/Model/Table/FiltersTable.php
];
/*
* Called via ajax. Renders the data and pagination to a template.
*/
public function getFilters()
{
$this->viewBuilder()->setLayout('ajax');
$this->loadModel('Filters');
$rf_keywords = '';
$rf_keywords = trim($this->request->getData('rf_keywords'));
// Pagination settings (relevant to the question).
$page = $this->request->getData('page') ? (int)$this->request->getData('page') : 1;
$this->paginate = ['limit' => 200, 'page' => $page];
$finder = $this
->Filters
->find('filters' , [
'rf_keywords' => $rf_keywords
]);
$data = $this
->paginate($finder)
->toArray();
$this->set('data', $data);
}
In my template (get_filters.ctp) I have the following to output the pagination numbers (links to be clicked to go between pages) and the total counts:
<?= $this->Paginator->numbers(); ?>
<?= $this->Paginator->counter([
'format' => 'Page {{page}} of {{pages}}, showing {{current}} records out of
{{count}} total, starting on record {{start}}, ending on {{end}}'
]) ?>
The problem is that changing the limit in the Controller...
$this->paginate = ['limit' => 200];
... is not updating the output of the Pagination correctly. See examples below:
'limit' => 20:
Shows page numbers 1 - 9 and:
Page 1 of 49, showing 20 records out of 972 total, starting on record 1, ending on 20
'limit' => 100:
Shows page numbers 1 - 9 and:
Page 1 of 10, showing 100 records out of 972 total, starting on record 1, ending on 100
'limit' => 500:
Shows page numbers 1 - 9 and:
Page 1 of 10, showing 100 records out of 972 total, starting on record 1, ending on 100
'limit' => 1000 (more than there are rows in the DB!):
Shows page numbers 1 - 9 and:
Page 1 of 10, showing 100 records out of 972 total, starting on record 1, ending on 100
So there are 2 problems:
It doesn't work with any limit value >100. Nothing changes in terms of the pagination output in the template. The behaviour is as though it is ignoring anything supplied as the limit and defaulting to 100.
The page number links ($this->Paginator->numbers();) are always 1 - 9 when the limit is >100. That is to say they stay the same irrespective of whether I use a limit of 100, 200, 500, 1000. I'm guessing this is due to problem (1) where it seems to ignore the actual limit set if it's >100.
I thought this was some caching issue. The ajax requests to getFilters() are being made via jquery so I have set cache: false on them to ensure each request appends a timestamp to the URL to ensure it's not using some old cached response.
I have cleared the files in tmp/cache/models/*
I've a force refresh in the browser. Tried an incognito/private browsing window to make sure it's not some session issue.
None of these things solve the problem.
The issue is also consistent if the page part of Pagination is altered. For example using page => 3 will give the correct rows from the database for page 3. But the Pagination HTML will still be exactly the same as where the problem occurs.
Why isn't this working?
Edit - the custom Finder returns the correct data so I don't think this is an issue. The signature is below and it returns a Query object which is executed via Pagination:
// src/Model/Table/FiltersTable.php
public function findFilters(Query $query, array $options)
{
// $query->find() ...
// ...
return $query;
}
Bit late, but the problem is with default maxLimit settings for CakePHP 3 pagination, what is set to 100 by default (reference below).
https://book.cakephp.org/3/en/controllers/components/pagination.html#limit-the-maximum-number-of-rows-per-page
When you update settings in Controller to, it should do the trick.
public $paginate = [
// Other keys here.
'maxLimit' => 1000
];
I have this problem, when i manually create a paginator in laravel for show 100 products, in the view the page displays the data and it is fine, but if i put a limit , example i want 10 element per page, he show the ten elements in the firs page, when i click in next the second page show me the same ten elements, the data don't changes , why?
Controller:
public function show()
{
$client = new Client([
// Base URI is used with relative requests
'base_uri' => 'http://www.mocky.io/v2/59bec4d926000046015261a7',
// You can set any number of default request options.
'timeout' => 2.0,
]);
$response = $client->request('GET', '');
$code = $response->getStatusCode()
$products = json_decode($response->getBody()->getContents());
}
$products = new Paginator($products, 10 ,
Paginator::resolveCurrentPage(),
['path' => Paginator::resolveCurrentPath()]);
return view('products/list', compact('products'));
}
View
#extends('layout.master')
#section('content')
<h2> Products</h2>
<ul>
#if($products)
#foreach($products as $product)
<li> {{ $product->name}} - {{ $product->value}}</li>
#endforeach
#endif
</ul>
{{$products->render()}}
#endsection
Example of Result with array of ten element , 3 per page
// this is a example with invented information.
array {0,1,2,3,4,5,6,7,8,9}
Page 1
0 - 0
1 - 1
2 - 2
Page 2 // the data dont change , why ?
0 - 0
1 - 1
2 - 2
No magic, paginators will call your controller function for every page. The request will have the pagination information in it. It is your job to actually select and slice the page. The paginator simply presents it... which is a big part of the work...
// DB::select returns an array, thus we have to build the paginator ourselves...
$comm = DB::select('select bla bla bla from comments where this and that...
order by approved ASC');
// this basically gets the request's page variable... or defaults to 1
$page = Paginator::resolveCurrentPage('page') ?: 1;
// Assume 15 items per page... so start index to slice our array
$startIndex = ($page - 1) * 15;
// Length aware paginator needs a total count of items... to paginate properly
$total = count($comm);
// Eliminate the non relevant items...
$results = array_slice($comm, $startIndex, 15);
$comments = new LengthAwarePaginator($results, $total, 15, $page, [
'path' => Paginator::resolveCurrentPath(),
'pageName' => 'page',
]);
return view('backend/comments', compact('comments'));
You need to add your page name (the name of the request param denoting the page number) like so:
$products = new Paginator($products, 10, null,
['path' => Paginator::resolveCurrentPath(),
'pageName' => 'page']);
I had the same problem, where the data was not changing. I solved it by passing a page number to my guzzle call. Whenever a link is clicked, a request is sent therefore, you can get the page number from the request object. Pass the page number to the guzzle call so that the page number changes. Hope It helps
I'm really lost on how pagination works in kohana 3. Is there a good example of pagination in Kohana 3 anywhere?
// Get the total count of articles
$count = $this
->_profil
->articles
->count_all();
// Create the pagination object
$pagi = Pagination::factory(array(
'items_per_page' => 4,
'total_items' => $count,
));
// Find actual articles
$articles = $this->_profil
->articles
->join_categories()
->order_by('id','DESC')
->limit($pagi->items_per_page)
->offset($pagi->offset)
->find_all();
and then in the View, you just do
echo $pagi; // ofc, after passing the Pagination object to view
What happens here is Pagination class using it's View's __toString() magic method to render html needed to display pagination. All pagination params can be modified when creating the object (passing appropriate keys to the array passed to factory() method in our case).
Default key for pagination is "page" (query string), while you can modify that as well. Pagination also has a default config, which you can override by copying it to application/config folder.
Enjoy using it :)
In Kohana 3.1 pagination is not included. Download the module and put it in the modules folder. Enable the module in your application/bootstrap.php .This is my controller page. For further configuration copy the provided config file from modules/pagination/config/pagination.php to application/config/pagination.php
$per_page =2;
$page_num = $this->request->param('page', 1);
$offset = ($page_num - 1) * $per_page;
$view =View::factory('image/imagelist')->bind('page_links',$page_links)->bind('results', $results)->bind('pagination', $pagination);
// Get the total count of records in the database
$userid = Auth::instance()->get_user()->pk();
$count=ORM::factory('user_image')->where('app_userid','=',$userid)->count_all();
// Create an instance of Pagination class and set values
$pagination = Pagination::factory(array(
'total_items' => $count,
'current_page' => array('source' => 'image/imagelist', 'key' => 'page'),
'items_per_page' => $per_page,
'offset' => $offset,
'view' => 'pagination/basic'
));
// Load specific results for current page
$results = DB::select()->from('user_images')
->where('app_userid','=',$userid)
->order_by('image_id','ASC')
->limit($pagination->items_per_page)
->offset($pagination->offset)->execute();
$page_links = $pagination;
$this->template->content=$view->render();
You may get error ErrorException [ Notice ]: Undefined property: Request::$uri. in the pagination class (module). In order to fix fix it
Use Request::current()->uri() instead of Request::current()->uri
You can find some decent docs in the unofficial Kohana wiki.
I have a query that running way too slow. the page takes a few minutes to load.
I'm doing a table join on tables with over 100,000 records. In my query, is it grabbing all the records or is it getting only the amount I need for the page? Do I need to put a limit in the query? If I do, won't that give the paginator the wrong record count?
$paymentsTable = new Donations_Model_Payments();
$select = $paymentsTable->select(Zend_Db_Table::SELECT_WITH_FROM_PART);
$select->setIntegrityCheck(false)
->from(array('p' => 'tbl_payments'), array('clientid', 'contactid', 'amount'))
->where('p.clientid = ?', $_SESSION['clientinfo']['id'])
->where('p.dt_added BETWEEN \''.$this->datesArr['dateStartUnix'].'\' AND \''.$this->datesArr['dateEndUnix'].'\'')
->join(array('c' => 'contacts'), 'c.id = p.contactid', array('fname', 'mname', 'lname'))
->group('p.id')
->order($sortby.' '.$dir)
;
$payments=$paymentsTable->fetchAll($select);
// paginator
$paginator = Zend_Paginator::factory($payments);
$paginator->setCurrentPageNumber($this->_getParam('page'), 1);
$paginator->setItemCountPerPage('100'); // items pre page
$this->view->paginator = $paginator;
$payments=$payments->toArray();
$this->view->payments=$payments;
Please see revised code below. You need to pass the $select to Zend_Paginator via the correct adapter. Otherwise you won't see the performance benefits.
$paymentsTable = new Donations_Model_Payments();
$select = $paymentsTable->select(Zend_Db_Table::SELECT_WITH_FROM_PART);
$select->setIntegrityCheck(false)
->joinLeft('contacts', 'tbl_payments.contactid = contacts.id')
->where('tbl_payments.clientid = 39')
->where(new Zend_Db_Expr('tbl_payments.dt_added BETWEEN "1262500129" AND "1265579129"'))
->group('tbl_payments.id')
->order('tbl_payments.dt_added DESC');
// paginator
$paginator = new Zend_Paginator(new Zend_Paginator_Adapter_DbTableSelect($select));
$paginator->setCurrentPageNumber($this->_getParam('page', 1));
$paginator->setItemCountPerPage('100'); // items pre page
$this->view->paginator = $paginator;
Please see revised code above!
In your code, you are :
first, selecting and fetching all records that match your condition
see the select ... from... and all that
and the call to fetchAll on the line just after
and, only the, you are using the paginator,
on the results returned by the fetchAll call.
With that, I'd say that, yes, all your 100,000 records are fetched from the DB, manipulated by PHP, passed to Zend_Paginator which has to work with them... only to discard almost all of them.
Using Zend_Paginator, you should be able to pass it an instance of Zend_Db_Select, and let it execute the query, specifying the required limit.
Maybe the example about DbSelect and DbTableSelect adapter might help you understand how this can be achieved (sorry, I don't have any working example).
I personally count the results via COUNT(*) and pass that to zend_paginator. I never understood why you'd deep link zend_paginator right into the database results. I can see the pluses and minuses, but really, its to far imho.
Bearing in mind that you only want 100 results, you're fetching 100'000+ and then zend_paginator is throwing them away. Realistically you want to just give it a count.
$items = Eurocreme_Model::load_by_type(array('type' => 'list', 'from' => $from, 'to' => MODEL_PER_PAGE, 'order' => 'd.id ASC'));
$count = Eurocreme_Model::load_by_type(array('type' => 'list', 'from' => 0, 'to' => COUNT_HIGH, 'count' => 1));
$paginator = Zend_Paginator::factory($count);
$paginator->setItemCountPerPage(MODEL_PER_PAGE);
$paginator->setCurrentPageNumber($page);
$this->view->paginator = $paginator;
$this->view->items = $items;