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
));
}
Related
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');
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'
)
);
I created a custom find type, and am trying to paginate the results, but the paginator seems to be ignoring the findType setting. Can someone tell me what I'm doing wrong?
(CakePHP 2.X)
In my Controller:
public function list($username=null) {
$this->Paginator->settings = array(
'Question' => array(
'findType' => 'unanswered',
'conditions' => array('Question.private' => 0),
);
$data = $this->Paginator->paginate('Question');
$this->set('data', $data);
);
Custom find type setup in my Model:
public $findMethods = array('unanswered' => true);
protected function _findUnanswered($state, $query, $results = array()) {
if ($state == 'before') {
$query['order'] = array('Question.created DESC');
$query['conditions'] = array_merge($query['conditions'], array('Question.date_answered' => ''));
return $query;
$this->log($query);
} elseif ($state == 'after') {
return $results;
}
}
Edit
I can paginate the query if I remove $this->Paginate->settings, and replace it with this:
$this->paginate = array('unanswered');
However, I want to add some additional conditions., this doesn't work:
$this->paginate = array('unanswered' => 'conditions' => array('Question.user_id' => $id, 'limit' => 4)) );
Is this possible?
findType was added to the paginator component in CakePHP 2.3, I was on 2.0
http://api.cakephp.org/2.3/class-PaginatorComponent.html
I've set a simple search engine on my CakePHP project which looks like that :
<?php
echo $this->Form->create("Post", array(
"action" => "search",
"id" => "searchForm"
));
echo $this->Form->input("keyword", array(
"label" => "",
"type" => "search",
"placeholder" => "Recherche..."
));
echo $this->Form->end();
?>
Here is the controller :
function search() {
$keyword = $this->request->data;
$keyword = $keyword["Post"]["keyword"];
$cond = array("OR" => array(
"Post.title LIKE '%$keyword%'",
"Post.description LIKE '%$keyword%'"
));
$posts = $this->Post->find("all", array("conditions" => $cond));
$this->set(compact("posts", "keyword"));
}
And it works great. The only problem is when I want to paginate the results. I simply add :
$posts = $this->paginate();
And here is the problem. When I add this, CakePHP give me all the posts and not only the ones that match the keyword.
So, if you would have a solution, it would be nice :)
According to the CakePHP book you should be able to do
$this->paginate('Post', array(
'OR' => array(
'Post.title LIKE' => "%$keyword%",
'Post.description LIKE' => "%$keyword%"
)
));
Or you can do it like this ( from the cakephp site ).
public function list_recipes() {
$this->paginate = array(
'conditions' => array('Recipe.title LIKE' => 'a%'),
'limit' => 10
);
$data = $this->paginate('Recipe');
$this->set(compact('data'));
);
Source:
http://book.cakephp.org/2.0/en/core-libraries/components/pagination.html
Paginate does its own lookup on the data I believe. The find you are calling previously has no effect on the paginate.
Try this:
$this->paginate = array( 'conditions' => $cond, ));
you can use Session for storing conditions
the first, when you submit form you store conditions into Session
and then(paginate) you can read conditions from Session
example i want to search the products:
Products/search.ctp
<?php
echo $this->Form->create('Product');
echo $this->Form->input('keyword');
echo $this->Form->end(__('Search'));
?>
ProductsController.php
<?php
class ProductsController extends AppController{
public function search() {
if ($this->request->is('post')) {
$keyword = $this->request->data['Product']['keyword'];
$this->paginate = array(
'fields' => array('Product.name', 'Product.price', 'Product.created'),
'order' => array('Product.created' => 'DESC', 'Product.price' => 'DESC'),
'limit' => 30,
'conditions' => array('Product.name LIKE' => '%' . $keyword . '%')
);
// store array $this->paginate into Session
$this->Session->write('paginate', $this->paginate);
}
$this->paginate = $this->Session->read('paginate');
$this->set('products', $this->paginate('Product'));
}
}
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.