CakePHP 3: How to use custom pagination? - php

I don't succeed in using the pagination with a custom query. I follow exactly what's in the doc, but it doesn't work. The point is to paginate a list of records, having the flag 'valid' to 'Y' or 'N'.
In the controller:
if (!isset($this->request->query['valid']) || $this->request->query['valid'] == '')
$allFilters['valid'] = 'N';
else
$allFilters['valid'] = 'Y';
$this->paginate = ['finder' => ['curnames' => $allFilters]];
$data = $this->paginate($this->Names)->toArray();
In the model:
public $paginate = ['finder' => 'curnames', 'limit' => 25, 'order' => ['Names.id' => 'asc']];
public function findCurnames(Query $query, array $options) {
$query->where([
'valid' => $option['valid']
]);
return $query;
}
When I execute the code, I get a Cake\Network\Exception\NotFoundException exception. What I am missing?
Update: The version is 3.3. The error is triggered when this is executed:
$data = $this->paginate($this->Names)->toArray();
Update : In the controller, I've change the line
$this->paginate = ['finder' => ['curnames' => $allFilters]];
to
$paginate = ['finder' => ['curnames' => $allFilters]];
and the error doesn't pop up anymore. But the condition filtering on the 'valid'='Y' or 'N' is not taken into account. So it still doesn't work.

I got the answer. The error comes from a typo. Correct code should be
'valid' => $options['valid']
Options with a 's'.
Now it works.

Isn't it easier to do something like this (without any custom finder method):
if ($this->request->getQuery('valid', '') == '')
$valid = 'N';
else
$valid = 'Y';
$this->paginate = [
'limit' => 25,
'order' => ['Names.id' => 'asc']
];
$query = $this->Names->find('all')
->where([
'valid' => $valid
]);
$data = $this->paginate($query)->toArray();

Related

CakePHP Pagination Routing Issues

Within CakePHP 2 I am using pagination which works great until I see the URL which is page:2, how can I make this ?page=2 ?
The next question is that I use this code for my controller which powers /domain.com/offers/top, /domain.com/offers/newest, /domain.com/offers/popular and then the categories like /domain.com/offers/tv-and-video. The thing is when it is paginated for /domain.com/offers/top instead of being /offers/top/page:2 it goes to /offers/bycategory/top/page:2.
public function bycategory($slug = null)
{
$userId = $this->Session->read("UserAuth.User.id");
if ($slug == 'top') {
//Get the top rated offers
$this->paginate = array(
'limit' => 15,
'order' => array(
'Offer.vote' => 'desc'
)
);
} elseif ($slug == 'newest') {
//Get the latest offers
$this->paginate = array(
'limit' => 15,
'order' => array(
'Offer.created' => 'desc'
)
);
} elseif ($slug == 'popular') {
//Get the most talked about offers
} else {
//This is the categories, so just get the category slug.
$this->paginate = array(
'conditions' => array('Category.slug =' => $slug),
'limit' => 15,
'order' => array(
'Offer.created' => 'desc'
)
);
}
$offers = $this->paginate('Offer');
// pass the value to our view.ctp
$this->set('offers', $offers);
$this->set('userId', $userId);
$this->render('/Offers/index');
}
This is my custom route:
Router::connect(
'/offers/:catslug',
array('controller' => 'offers', 'action' => 'bycategory'),
array(
'pass' => array('catslug')
));
how can I make this ?page=2 ?
By setting the paramType option in paginator component options as mentioned in manual.
You second issue looks like reverse routing issue. Have you setup any custom routes?

how to make chained model auto read the conditions?

I have two models called Batch and User
Batch has the following
public $belongsTo = array(
'Customer' => array(
'className' => 'User',
'foreignKey' => 'customer_id',
'conditions' => array('Customer.group_id' => CUSTOMERS),
'fields' => '',
'order' => '',
),
);
When I do the following:
$customers = $this->Batch->Customer->find('list');
I fully expected to get back just the users whose group_id matches CUSTOMERS. It returns ALL the records in the users table.
However, I actually have to write
$customers = $this->Batch->Customer->find('list', array('conditions' => array('Customer.group_id' => CUSTOMERS)));
Is there a way so that the chained model User knows that it is called as Customer by Batch and therefore automatically reads the correct conditions in the associations found in Batch model?
I want to make my code more readable hence the motivation for this question.
I want to write simply
$customers = $this->Batch->Customer->find('list');
or something similarly straightforward.
Of course, I realized that if I do the following:
$batches = $this->Batch->find('all');
The condition stated in the associations will be used. But I don't want to find batches. I want to find just customers.
I am using CakePHP 2.4
I think you can't
but you can create custom find types in User model file
public $findMethods = array('customer' => true); //this enable a custom find method named 'customer'
protected function _findCustomer($state, $query, $results = array()) {
if ($state === 'before') {
$query['conditions'] = array('group_id' => CUSTOMERS);
}
return parent::_findList($state, $query, $results);
}
and in BatchesController
$this->Batch->Customer->find('customer');
There are several ways to do this.
1)
do nothing.
Continue to use code like
$customers = $this->Batch->Customer->find('list', array('conditions' => array('Customer.group_id' => CUSTOMERS)));
2)
create a custom find method as suggested by arilia.
3)
write a getCustomers method inside Batch model
where it looks something like this:
public function getCustomers($type, $query = array()) {
if (empty($query['conditions'])) {
$query['conditions'] = array();
}
$query['conditions'] = array_merge($query['conditions'], array('Customer.group_id' => CUSTOMERS));
return $this->Customer->find($type, $query);
}
then you can call
$customers = $this->Batch->getCustomers('list');
UPDATE:
I have written a Plugin that helps with this kind of behavior, utilizing the 3rd solution.
class Batch extends AppModel {
public $name = 'Batch';
public $actsAs = array('UtilityBehaviors.GetAssoc');
public $belongsTo = array(
'Customer' => array(
'className' => 'User',
'foreignKey' => 'customer_id',
'conditions' => array('Customer.group_id' => 7),
'fields' => '',
'order' => '',
),
);
}
You can fetch just the customer data when you are in BatchesController this way:
$customers = $this->Batch->getAssoc('Customer', 'list');
$customers = $this->Batch->getAssoc('Customer', 'all');
$customerCount = $this->Batch->getAssoc('Customer', 'count');
This behavior has tests at travis and you can read about the tests written at github.

Two variables set to null inside ( ) of function

In my model I have this
function pieChart($conditions = null) {
//Get Data for PieChart
$this->RecordDrug->virtualFields['sum'] ='COUNT(*)';
$records = array();
$records=$this->RecordDrug->find('list',
array(
'conditions' => $conditions,
'fields' => array( 'Drug.drug', 'sum'),
'order' => array('sum' => 'desc'),
'contain' => array( 'Drug', 'Record' ),
'group' => 'Drug.Drug'
));
return $records;
}
Which basically means that when this function is called by it's self, there are no conditions set. So inside my controller, I am able to define a condition, if i'd like. I want to do the exact same thing except with
'limit' => $limit,
and I assume I need to set
$limit = null
Inside the parenthesis of the function. I've tried & and , and $limit,$conditions = null
but neither of these options worked. I am not too experience with OOP but I assume there is a way to do this?
EDIT:
Updated code, Still not working. The first varialbe that comes inside the functions parenthesis is the one that works, the second one just act's like it's not there
Model:
function pieChart($limit = null, $conditions = null) {
//Get Data for PieChart
$this->RecordDrug->virtualFields['sum'] ='COUNT(*)';
$records = array();
$records=$this->RecordDrug->find('list',
array(
'conditions' => $conditions,
'fields' => array( 'Drug.drug', 'sum','Record.unit'),
'order' => array('sum' => 'desc'),
'limit' => $limit,
'contain' => array( 'Drug', 'Record' ),
'group' => 'Drug.Drug'
));
debug($records);
return $records;
}
Controller:
$conditions = array('Record.user_id' => $this->Session->read('Auth.User.id'));
$pieChart = $this->Record->pieChart($conditions);
$this->set('output',$pieChart);
Even after this conditions variable, it does not only load the users data. If I were to remove '$limit = null' it will work as intended.
This way:
function pieChartTwo($limit = null, $conditions = null) {
...
}

Searching for data from model in CakePHP

I am working on my first project experimenting with CakePHP. Basically I have a site where I want users to be able to search through different workouts stored in a MySQL database.
When users enter a search term, I want to simply return any workouts whose names contain the search term. On top of that, I want them to be able to apply filters to narrow down the search to specific muscle groups.
The filters are passed to the search action through a querystring, and the search term is passed using the post method.
First off, here are the two models I care about right now:
class Workout extends AppModel {
public $name = 'Workout';
public $hasAndBelongsToMany = array(
'MuscleGroup' =>
array(
'className' => 'MuscleGroup',
'joinTable' => 'workouts_muscle_groups',
'foreignKey' => 'workout_id',
'associationForeignKey' => 'muscle_group_id',
'unique' => true,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
)
);
}
class MuscleGroup extends AppModel {
public $name = 'MuscleGroup';
public $hasAndBelongsToMany = array(
'Workout' =>
array(
'className' => 'Workout',
'joinTable' => 'workouts_muscle_groups',
'foreignKey' => 'muscle_group_id',
'associationForeignKey' => 'workout_id',
'unique' => true,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
)
);
}
Now below is the search action in my WorkoutsController:
function search() {
$hasFilters = false;
$filters = array(
"abs" => false,
"back" => false,
"biceps" => false,
"chest" => false,
"forearms" => false,
"legs" => false,
"shoulders" => false,
"triceps" => false
);
if(isset($this->params['url']['abs']))
if($this->params['url']['abs'] == "1")
$filters['abs'] = $hasFilters = true;
if(isset($this->params['url']['back']))
if($this->params['url']['back'] == "1")
$filters['back'] = $hasFilters = true;
if(isset($this->params['url']['biceps']))
if($this->params['url']['biceps'] == "1")
$filters['biceps'] = $hasFilters = true;
if(isset($this->params['url']['chest']))
if($this->params['url']['chest'] == "1")
$filters['chest'] = $hasFilters = true;
if(isset($this->params['url']['forearms']))
if($this->params['url']['forearms'] == "1")
$filters['forearms'] = $hasFilters = true;
if(isset($this->params['url']['legs']))
if($this->params['url']['legs'] == "1")
$filters['legs'] = $hasFilters = true;
if(isset($this->params['url']['shoulders']))
if($this->params['url']['shoulders'] == "1")
$filters['shoulders'] = $hasFilters = true;
if(isset($this->params['url']['triceps']))
if($this->params['url']['triceps'] == "1")
$filters['triceps'] = $hasFilters = true;
$query = $this->request->data['search'];
//insert code here to actually perform the search!
}
I was really hoping for an easy way to do this but I have been poking around the documentation all night and can't seem to find any relevant examples. In EntityFramework I could simply say something like:
List<Workout> results = context.Workouts.Where(w => w.Name.Contains(searchTerm) && w.MuscleGroups.Any(g => g.Id == mg_id)).ToList();
Note: no idea if that C# syntax is anywhere near correct, but the ease of accessing data from the model was somewhere close to that from what I remember.
How can I get the equivalent of that from CakePHP?
Try to use MuscleGroup IDs for filtering, instead of their names. I guess the user will select them from a list, anyway.
Then, the find() should work like this:
$this->Workout->find(
'all',
array(
'conditions' => array(
'Workout.name LIKE' => '%'.$searchTerm.'%',
// List of included MuscleGroups:
'MuscleGroup.id' => array(1, 5, 9, 17)
)
)
);
Note that you need to take care that the related MuscleGroup is included in the Workout query, either by setting $this->Workout->recursive = 1 or (recommended) by using the more flexible ContainableBehavior.

CakePHP Validation Rule Error

I've go some validation functions written to check if the user's email exists in the system.
I am getting the following error
Notice (8): Undefined offset: 0 [CORE/cake/libs/model/model.php, line 1122]
This is the code which causes the error
'email' => array(
'emailRule-1' => array(
'rule' => 'email',
'message' => 'email format is incorrect',
'last' => true
),
'emailRule-2' => array(
'rule' => 'checkEmailExist',
'message' => 'email already exists in the system'
)
),
And rule2 seems to be responsible for the error, and here is the rule2:
function checkEmailExist($emailAddress, $user_id){
$this->recursive = -1;
if($user_id > 0){
$user = $this->read(array('email'), $user_id);
if($emailAddress == $user['User']['email'])
return true;
}
$result = $this->find('count', array('conditions' => array('User.email' => $emailAddress)));
return $result > 0 ? false : true;
}
Why not do it like this?
public $validate = array(
'email' => array(
'rule' => array('email', 'isUnique')
)
);
You might want to split it up into two separate rules to apply your own error messages, but this should work just fine.
Did you try to debug what $emailAddress contains?
I bet this is an array^^
function checkEmailExist($emailAddress, $user_id){
$this->recursive = -1;
$email = array_shift(emailAddress);
...
you need to get the child element first
so remember: always a good idea to use debug() or pr() to debug your variables first.

Categories