CakePHP Pagination. Keeping Model Fat - cakephp

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.

Related

Associated model conditions being ignored

I have 2 models: Option and Modifier with such Option belongsTo Modifier relation:
public $belongsTo = array(
'Modifier' => array(
'className' => 'Modifier',
'foreignKey' => 'modifier_id',
'conditions' => array('Modifier.type' => '3'),
'fields' => 'Modifier.name',
'order' => 'Modifier.name ASC'
)
);
In OptionsController I try to get list of Modifiers:
$modifiers = $this->Option->Modifier->find('list');
And CakePHP generates SQL without conditions, so I get full list of Modifiers. Why CakePHP 2.4.4 ignores conditions, defined in model belongsTo relation? It also ignores fields and order.
this is the right behavior
when you write:
$modifiers = $this->Option->Modifier->find('list');
you are just accessing Modifier model and not all modifiers related to Option.
to achieve what you want you have to do this:
$modifiers = $this->Option->Modifier->find(
'list',
array('conditions' => array('Modifier.type' => '3')
)
you can also create your own find type (see manual)
class Modifier extends AppModel {
public $findMethods = array('type3' => true);
protected function _findType3($state, $query, $results = array()) {
if ($state === 'before') {
$query['conditions']['Modifier.type'] = 3;
return $query;
}
return $results;
}
}
and in your controller do this
$modifiers = $this->Option->Modifier->find('type3');

Cakephp Custom Find Type Pagination

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

cakephp model with hasmany

I have question in cakephp model,
I want to add dynamic condition in var $hasMany keyword
I want to add condition like current user_id, i got user Id after my login.
var $hasMany = array(
"AskComment"=>array('limit'=>3),
'AskStatistic',
'AskContactsLink',
'AskStatistic',
'AskObject',
'AskLikes'
);
If you want to add dynamic condition in your model, then you might have to bind the model association-ship dynamically into your controller's code. Write the following code into your controller's method for which you want to impose some new condition on the existing/new associated models.
$this->PrimaryModel->bindModel(array('hasMany' => array(
'AskComment' => array(
'className' => 'AskComment',
'foreignKey' => 'primary_id',
'conditions' => array('AskComment.user_id' => $user_id)
)
)
));
Take a look at this link: Creating and destroying associations on the fly. This will surely help you to achieve the same.
I think its better to put your association in the construct function of your Model.
like this:
/**
* #see Model::__construct
*/
public function __construct($id = false, $table = null, $ds = null) {
public $hasMany = array(
'AskComment' => array(
'className' => 'AskComment',
'foreignKey' => 'primary_id',
'conditions' => array(
'AskComment.user_id' => $user_id,
),
),
);
}

HABTM Find with CakePHP 2.0

I am trying to do a search, using pagination for posts which have a specific tag or tags (for example, if a user was to select two tags, then posts containing either tag would be returned).
I have the relationship defined in my Posts table
public $hasAndBelongsToMany = array('Tags' => array(
'className' => 'Tags',
'joinTable' => 'posts_tags',
'foreignKey' => 'post_id',
'associationForeignKey' => 'tag_id',
'unique' => 'keepExisting'));
How do I use Find to retrieve rows with a given tag (name or ID would be fine)
Trying:
// other pagination settings goes here
$this->paginate['conditions']['Tags.id'] = 13;
gives me an error that the relationship does not exist.
Looking at the debug info it appears that the tables are not joining the Posts_Tags and Tags table, however, when I debug the data making it to the view, the Posts objects contain the tags data.
Most of the documentation I can find for this seems to revolve around earlier versions of CakePHP, any help would be appreciated.
Could not find a satisfying solution myself.
I created a behavior to take care of this.
Create a file called HabtmBehavior.php and put it in your app/Model/Behavior folder.
Put the block of code in there and save file.
Add the behavior to your model: eg public $actsAs = array('Habtm');
Here is a usage example with find.
<?php $this->Entry->find('all', array('habtm'=>array('Tag'=>array('Tag.title'=>'value to find'))) ?>
Paginate would look something like this:
$this->paginate['Entry']['habtm']['Tag'] = array('Tag.title'=>'value to find');
You are free to add as many relations as you want by adding additional Model Names in the habtm array.
(Just be careful not to make it to complex since this could start slowing down your find results.)
<?php
class HabtmBehavior extends ModelBehavior {
public function beforeFind(Model $model, $options) {
if (!isset($options['joins'])) {
$options['joins'] = array();
}
if (!isset($options['habtm'])) {
return $options;
}
$habtm = $options['habtm'];
unset($options['habtm']);
foreach($habtm as $m => $scope){
$assoc = $model->hasAndBelongsToMany[$m];
$bind = "{$assoc['with']}.{$assoc['foreignKey']} = {$model->alias}.{$model->primaryKey}";
$options['joins'][] = array(
'table' => $assoc['joinTable'],
'alias' => $assoc['with'],
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array($bind)
);
$bind = $m.'.'.$model->{$m}->primaryKey.' = ';
$bind .= "{$assoc['with']}.{$assoc['associationForeignKey']}";
$options['joins'][] = array(
'table' => $model->{$m}->table,
'alias' => $m,
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array($bind) + (array)$scope,
);
}
return $options;
}
}
Hope this helps.
Happy baking.
I think the best solution is apply find function on join table Model. I try this before and it's work fine.
in your PostTag model :
/**
* #see Model::$actsAs
*/
public $actsAs = array(
'Containable',
);
/**
* #see Model::$belongsTo
*/
public $belongsTo = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'post_id',
),
'Tags' => array(
'className' => 'Tag',
'foreignKey' => 'tag_id',
),
);
in your controller :
// $tagsId = tags ids
$posts = $this->PostTag->find('all', array('conditions' => array('PostTag.tag_id' => $tagsId),'contain' => array('Post')));
also is better follow cake naming convention, if you have tags(plural), post_tags(first singular second plural),posts(plural) tables you must have Tag,PostTag,Post Models.

CakePHP HABTM accessing associated table for display?

For some reason i'm really having a hard time wrapping my head around HABTM associations. I learn best by watching someone do something and explaining why. Anyways, I have 2 tables I want associated, Drugs, and SideEffects. I've created the intermediate table drugs_side_effects(has no data right now). Does cake put the data in that automatically or do I need to do something? The 3.7.6.5 hasAndBelongsToMany (HABTM) from the book didn't specify.
I've set up the models correctly(I think) and am not sure how to proceed at this point. It seems pretty simple. I need to display side_effect from the SideEffects table in a Drug view. I think in the edit_french controller function i'll need something like
$side_effect = $this->Drug->SideEffect->read(
array('SideEffect.id','SideEffect.side_effect'), $id);
$this->set('side_effect',$side_effect);
but I feel like that won't work as expected. Or maybe there's a more efficient way? Any advice or help is appreciated.
Drug Model:
var $hasAndBelongsToMany = array(
'SideEffect' => array(
'className' => 'SideEffect',
'joinTable' => 'drug_side_effects',
'foreignKey' => 'drug_id',
'associationForeignKey' => 'side_effect_id'
)
);
}
?>
SideEffect Model:
var $hasAndBelongsToMany = array(
'Drug' => array(
'className' => 'Drug',
'joinTable' => 'drug_side_effects',
'foreignKey' => 'side_effect_id',
'associationForeignKey' => 'drug_id'
)
);
}
?>
Drugs Controller:
<?php
class DrugsController extends AppController {
var $name = 'Drugs';
var $helpers = array('Html','Form','Paginator');
var $paginate = array(
//'contain' => array('SideEffect'),
//'fields' => array('Drug.id', 'Drug.generic'),
'fields' => array('Drug.id', 'Drug.generic','Drug.date_altered'),
'limit' => 50,
'order' => array(
'Drug.generic' => 'asc'
)
);
function index() {
$data = $this->paginate('Drug');
$alldrugs = $this->set('drugs', $this->Drug->find('all'));
$this->set('drugs', $data);
$this->set('alldrugs', $data);
//$this->set('lessdrugs', $this->paginate());
$this->set('title_for_layout','List of all current drugs');
}
function edit_french($id = null) {
$this->Drug->id = $id;
$drug = $this->Drug->read(
array(
'Drug.id','Drug.generic','Drug.ahl','Drug.aap','Drug.rid','Drug.oral','Drug.mw','Drug.clinical_recommendations'
),
$id
);
$this->set('title_for_layout', 'Translate clinical recommendations - ' . $drug['Drug']['generic']);
$this->set('drug',$drug);
if (empty($this->data)) {
$this->data = $this->Drug->read();
} else {
if ($this->Drug->save($this->data)) {
$this->Session->setFlash('The drug has been updated.');
$this->redirect(array('action' => 'index'));
}
}
}
}
?>
You can pull the related data using ContainableBehavior. To do so, simply run a find on the Drug model and tell it to contain the associated SideEffect data.
$drug = $this->Drug->find('first', array(
'conditions' => array(
'Drug.id' => $id
),
'contain' => array(
'SideEffect'
)
));
You can also set the contain before using read() if you prefer.
$this->Drug->contain(array('SideEffect'));
$drug = $this->Drug->read(null, $id);
Using Containable allows you to gather all associated data in a single find() request.

Resources