I'm making an application with AngularJS & CakePHP.
I have one web service in Cake that might return data ordered but it doesn't do that, it returns data as came from DB.
This is part of my code:
$this->Paginator->settings = array('limit' => 20, 'page' => $this->request->data['page']+1, 'recursive' => -1);
$data = $this->Paginator->paginate('Country', $conditions, $order);
Anyone have an idea?
Here's the signature of Paginator's paginate component (See http://book.cakephp.org/2.0/en/core-libraries/components/pagination.html#custom-query-pagination):
public function paginate(Model $model, $conditions, $fields, $order, $limit,
$page = 1, $recursive = null, $extra = array())
The second argument you should be passing is fields, not order.
However, you can also just define the order in your settings array, like you do with a find condition.
Try this
$this->paginate = array(
'limit' => 20,
'conditions'=>$conditions,
'order' => $order
);
$data = $this->paginate('Country');
Related
For CakePhp 2.5
I have the following search function in my Controller.
public function search($query = null, $lim = null)
{
$tokens = explode(" ", $query);
$this->Paginator->settings = array(
'conditions' => array(
'Customer.site_id LIKE' => '%' . $this->viewVars['shopId'],
'CONCAT(Customer.first_name," ",Customer.last_name," ",Customer.organisation) LIKE' => '%' . implode(' ', $tokens) . '%'
),
'limit' => $lim
);
$this->set('customers', $this->Paginator->paginate());
}
This works fine, and gets the results i want.
However, its been suggested to me that I should put these search functions in my model. I can easily do this, and have done so similar as follows in my model:
public function getActiveTasks($id){
return $this->Task->find('all', array(
'conditions' => array(
'Task.customer_id' => $id,
'Task.status' => 0
),
'order' => array('Task.due_date ASC'),
'recursive' => -1,
));
}
The issue I'm having is that I cannot (or don't know how to) use Paginator with custom searches. Is there a way to paginate results from custom functions?
ie, can I do the following:
public function CustomModelSearch($query = null, $lim = null)
{
return $this->Task->find('all', array(
'conditions' => array(
'Customer.site_id LIKE' => '%' . $this->viewVars['shopId'],
'CONCAT(Customer.first_name," ",Customer.last_name," ",Customer.organisation) LIKE' => '%' . implode(' ', $tokens) . '%'
),
'limit' => $lim
));
}
and then in the controller call
$results = $this->Model->CustomModelSearch($query, $lim);
$this->set('results',$this->Paginate($results));
I cannot find a way to pass results into the paginator to render paged results to the view, unless I use it via the controller as per the first piece of code, which i've been told isn't good MVC principles.
To create custom pagination you have two options:
Create a Custom Query Pagination with a paginate and paginateCount funtions
// paginate and paginateCount implemented on a behavior.
public function paginate(Model $model, $conditions, $fields, $order, $limit,
$page = 1, $recursive = null, $extra = array()) {
// method content
}
public function paginateCount(Model $model, $conditions = null, $recursive = 0,
$extra = array()) {
// method body
}
or create a custom find and set Paginator to use that find.
good to understand but the truth should tell me that bug hits you in what you're doing.
The function in the model of "Task" you should not put $this->task->findjust$this->find that if you are in the model not need specify which model you are using, ou only need to do this in the controller.
model/Task.php:
public function CustomModelSearch($query = null, $lim = null)
{
return $this->find('all', array(
'conditions' => array(
'Customer.site_id LIKE' => '%' . $this->viewVars['shopId'],
'CONCAT(Customer.first_name," ",Customer.last_name," ",Customer.organisation) LIKE' => '%' . implode(' ', $tokens) . '%'
),
'limit' => $lim
));
}
I am making simple pagination which using this code:
$paginate = array(
'limit' => 30,
'fields' => array('DISTINCT Doctor.id','Doctor.*'),
'order' => array('Doctor.id' => 'desc'),
'joins' => array(
array('table' => 'doctors_medical_degrees',
'alias' => 'DoctorsMedicalDegree',
'type' => 'INNER',
'conditions' => array(
'Doctor.id = DoctorsMedicalDegree.doctor_id',
)
),
),
'recursive' => -1,
);
$this->Paginator->settings = $paginate;
$data = $this->Paginator->paginate('Doctor');
Now the problem is I am using Inner join so for Distinct result I am using Distinct Doctor.id, but the cakephp when doing query for pagination the count query not including Distinct Doctor.id
'query' => 'SELECT COUNT(*) AS `count` FROM `pharma`.`doctors` AS `Doctor` INNER JOIN `pharma`.`doctors_medical_degrees` AS `DoctorsMedicalDegree` ON (`Doctor`.`id` = `DoctorsMedicalDegree`.`doctor_id`)'
as you can see No
COUNT(DISTINCT Doctor.id)
so pagination return more number of result which it can actually return for
The problem is that the paginator doesn't pass the fields to the find('count') call, so by default it will always count on *.
But even if it would pass the fields, passing an array would make the find('count') call expect that the field to count is passed as a COUNT() expression, ie something like
'fields' => array('COUNT(DISTINCT Doctor.id) as `count`')
However that won't work with the paginator anyways, so what you need is a customized find('count') call.
Custom query pagination to the rescue
See Cookbook > Pagination > Custom Query Pagination for more information.
Custom query pagination is probably your best bet, that way it's totally up to you how counting is being done.
For example you could make use of the extra values passed by the paginator component, that way you could pass the field to count on to the find('count')` call, something like this (untested example code):
class Doctor extends AppModel {
// ...
public function paginateCount($conditions = null, $recursive = 0, $extra = array()) {
$parameters = compact('conditions');
if($recursive != $this->recursive) {
$parameters['recursive'] = $recursive;
}
if(!empty($extra['countField'])) {
$parameters['fields'] = $extra['countField'];
unset($extra['countField']);
}
return $this->find('count', array_merge($parameters, $extra));
}
}
$this->Paginator->settings = array(
'limit' => 30,
'fields' => array('DISTINCT Doctor.id','Doctor.*'),
// ...
'countField' => 'DISTINCT Doctor.id'
);
$data = $this->Paginator->paginate('Doctor');
This should then create a COUNT query that looks like
SELECT COUNT(DISTINCT Doctor.id) AS `count` ...
I found the solution on CakePHP - Pagination total count differs from actual count when using DISTINCT
Add the public function paginateCount in your model and use the distinct option in your paginator like:
$this->paginate = array('limit' => $limit, 'order' => array('Item.created' => 'ASC', 'Item.code' => 'ASC'),
'fields' => 'DISTINCT Item.*',
'conditions' => $conditions,
'countField' => array('Item.id'),
'joins' => $joins,
'distinct' => 'Item.id',
'contain' => array(
'Image' => array('limit' => 1),
'ItemsTag'
)
);
Currently, I am working on Cakephp Application. I want to first paginate and then sort the paginated data according to the condition show below or the other way around. First sort the data and then paginate it. Any hints on how to approach the problem.
I am just one week familiar with cakephp.
$condition[] = 'Banner.customer_id = "'.$loggedUserId.'"';
$this->Banner->recursive = 2;
$this->paginate = array(
'limit' => 20,
);
$data = $this->paginate('Banner', $condition);
$data_sorted = $this->Banner->find('all',array('order'=>array("Banner.created DESC")));
$this->set('loggedInUserId', $loggedUserId);
$this->set('savecrit', $savecrit);
$this->set('Banners', $data_sorted);
Try this:
$this->paginate = array(
'conditions' => array('Banner.customer_id' => $loggedUserId),
'limit' => 20,
'order' => array('id' => 'DESC'),
);
I currently have this in my Model (Referer Model):
public function getReferers($type = 'today') {
if ($type == 'this_month') {
return $this->_getThisMonthsReferers();
} elseif ($type == 'today') {
return $this->_getTodaysPageReferers();
}
}
private function _getThisMonthsReferers() {
$today = new DateTime();
return $this->Visitor->find('all', array(
'fields' => array(
'Referer.url',
'COUNT(UserRequest.visitor_id) as request_count',
'COUNT(DISTINCT(Visitor.id)) as visitor_count',
'COUNT(UserRequest.visitor_id) / COUNT(DISTINCT(Visitor.id)) as pages_per_visit',
'COUNT(DISTINCT(Visitor.id)) / COUNT(UserRequest.visitor_id) * 100 as percent_new_visit'
),
'joins' => array(
array(
'table' => 'user_requests',
'alias' => 'UserRequest',
'type' => 'RIGHT',
'conditions' => array(
'UserRequest.visitor_id = Visitor.id'
)
)
),
'conditions' => array(
'Visitor.site_id' => $this->Site->id,
'MONTH(UserRequest.created)' => $today->format('m'),
'YEAR(UserRequest.created)' => $today->format('Y')
),
'group' => array(
'url'
)
));
}
The thing is that I how I would paginate this. It will be so easy if just copy my code out of the model and to the controller. The thing is I want the keep the query in my Model.
How is this supposed to be done in CakePHP?
A custom find type is one method. You can find more information here: http://book.cakephp.org/2.0/en/core-libraries/components/pagination.html#custom-query-pagination
To turn your _getThisMonthsReferers into a custom find, follow this http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#creating-custom-find-types
For example:
// model
protected function _findThisMonthsReferers($state, $query, $results = array()) {
if ($state === 'before') {
$query['fields'] = ....
$query['joins'] = ....
return $query;
}
return $results;
}
// controller
public $paginate = array('findType' => 'thisMonthsReferers')
EDIT:
I think it should be :
public $paginate = array('thisMonthsReferers');
However the Solution I used derived from this answer is adding this to the method I am using
$this->paginate = array('thisMonthsReferers');
Since I don't want i used in all my actions. Then paginating the Model like this.
$this->paginate('Visitor);
Instead of returning the results of the find, just return it's array of options:
return array(
'fields' => array(
//...etc
Then use those options to paginate in the controller. More details on this answer of this similar question: Paginate from within a model in CakePHP
It still keeps the model fat (with any logic that might alter the conditions, joins, fields...etc), and the controller skinny, which just uses the returned array as paginate options.
My pagination worked perfectly like this :
var $paginate = array(
'Article' => array(
'conditions' => array(
'Article.visible' => true),
'order' => array('Article.creation_date DESC', 'Article.id DESC'),
'limit' => 11
)
);
But I want to filter my articles with a publication date like this
var $paginate = array(
'Article' => array(
'conditions' => array(
'Article.visible' => true,
'Article.publication_date <= ' => date('Y-m-d H:i:s')),
'order' => array('Article.creation_date DESC', 'Article.id DESC'),
'limit' => 11
)
);
But the date() function does not seem to be accepted.
It's not the good syntax.
Does anyone can help me?
Thanks a lot in advance.
If you want to do this, you will have to use $this->paginate = array() either in the action that you are paginating in, or in the __construct() function.
You cannot execute functions like that when the object is instantiated.
This is true for any type of method call at runtime.
You can't use a function in the declaration of an array at the class level. What you could do is to use a beforeFilter() callback in your controller to initialize the $paginate variable like this:
function beforeFilter()
{
parent :: beforeFilter();
$this->paginate = array(
'Article' => array(
'conditions' => array(
'Article.visible' => true,
'Article.publication_date <=' => date('Y-m-d H:i:s')),
'order' => array('Article.creation_date DESC', 'Article.id DESC'),
'limit' => 11
)
);
}