I am reading the documentation and there seems to be no example of how to use the beforeFind in Cakephp 3. I thought maybe I could do it like I did it in 2, but that did not work. Here are the two ways I did it.
public function beforeFind(Event $event, Query $query, array $options, $primary){
$primary = (bool) $primary;
if(!empty($options['pure'])) {
$query->hydrate(false)
->join([
'table' => 'main_'.$this->store_id,
'alias' => 'c',
'type' => 'LEFT',
'conditions' => 'c.id = Stores.MAIN_ID',
]);
}
}
Second way:
public function initialize(array $config) {
if(!isset($config['store_id'])) throw new Exception('You must provide a store id');
$this->store_id = $config['store_id'];
$this->entityClass('P1\Model\Entity\Store');
$this->eventManager()->attach([$this,'addMain'],'beforeFind');
}
public function addMain(Event $event, Query $query, array $options, $primary){
$primary = (bool) $primary;
if(!empty($options['pure'])) {
$query->hydrate(false)
->join([
'table' => 'main_'.$this->store_id,
'alias' => 'c',
'type' => 'LEFT',
'conditions' => 'c.id = Stores.MAIN_ID',
]);
}
}
What am I doing wrong?
You must use method applyOptions from class Query.
$this->Posts->find()->applyOptions(['pure'=>true])->all()
Related
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');
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.
Wow, CakePHP really hasn't got this problem sorted.
After hours of searching I came across the solution below (which may or may not be outdated), but I'm having issues applying paginatior 'limit' => 10 or other ordering.
Any ideas what I'm missing?
My model:
public $hasAndBelongsToMany = array(
'Post' => array(
'className' => 'Post',
'joinTable' => 'tags_posts',
'foreignKey' => 'tag_id',
'associationForeignKey' => 'post_id',
'order' => array('Post.created DESC'),
'unique' => true
)
);
In my controller in view()
public function view($id) {
$this->Tag->bindModel(array('hasOne' => array('TagsPost')), false);
$this->set('tag', $this->paginate('Tag', array('TagsPost.tag_id' => $id)));
}
In my view I then had to change:
foreach ($tag['Post'] as $post)
to
foreach ($tag[0]['Post'] as $post)
Your view method should looks like:
public function view($id) {
$this->Paginate['limit'] = 10;
$this->Paginate['conditions'] = array('TagsPost.tag_id' => $id);
$this->Tag->bindModel(array('hasOne' => array('TagsPost')), false);
$this->set('tag', $this->paginate('Tag'));
}
Kindly ask if it not worked for you.
Tested with CakePHP 2.x
public function view ($id)
{
$this->paginate = array (
'limit' => 10,
'conditions' => array('TagsPost.tag_id' => $id)
);
$this->Tag->bindModel(array('hasOne' => array('TagsPost')), false);
$this->set('tag', $this->paginate('Tag'));
}
Controller:
$data =
array(
'ContentI18n' =>
array(
0 =>
array(
'title' => 'first',
'author' => 'first',
'source' => 'sgfsdfrst',
'lang' => 'fa',
),
),
'Content' =>
array(
'publish' => 1,
'type' => 3,
'pages' => 8,
'volume' => 7,
'modified' => '2012-05-27 14:16:37',
'created' => '2012-05-27 14:16:37',
'lang' => 'fa',
),
);
$this->Content->create();
$this->Content->saveAll($data);
Model:
public $hasMany = array(
'ContentI18n' => array(
'className' => 'ContentI18n',
)
);
beforeSave function in behavior:
public function beforeSave(Model $model) {
// Define the new Translate model
App::uses($model->name . 'I18n', 'Model');
$varI18n = $model->name . 'I18n';
$modelI18n = new $varI18n;
foreach ($model->data[$model->name] as $key => $data) {
if (!in_array($key, $this->setting))
$modelData[$model->name][$key] = $data;
else
$modelData[$model->name . 'I18n'][0][$key] = $data;
}
$modelData[$model->name . 'I18n'][0]['lang'] = $model->locale;
$modelData[$model->name]['lang'] = $model->locale;
$model->data = $modelData;
//pr($model->data);
return TRUE;
}
every things seems be cause when is save it directly it's save with saveAll. but when i use same structure of data in behavior did not work without any error.
I found out that beforeSave callback will not trigger by saveAll. for executing some code before saveAll we must override saveAll function in our model.
public function saveAll($data, $options = array()) {
/*
your code you want execute before saving...
*/
parent::saveAll($data, $options);
}
for other saving methods such as saveMany,saveAssociated,... beforeSave trigger by them.
Model
<?php
class Tonguetwister extends AppModel {
var $name = 'Tonguetwister';
//The Associations below have been created with all possible keys, those that are not needed can be removed
var $belongsTo = array(
'language' => array(
'className' => 'language',
'foreignKey' => 'language_alias',
'dependent'=> true
)
);
}
?>
Controller
<?php
class TonguetwistersController extends AppController {
var $name = 'Tonguetwisters';
var $uses = array('Tonguetwister', 'Language');
function index() {
$this->set('languages', $this->Language->find('all'));
}
function view($id = null) {
if (!$id) {
$this->Session->setFlash(__('Invalid tonguetwister', true));
$this->redirect(array('action' => 'index'));
}
$this->set('tonguetwisters', $this->Tonguetwister->find('all', array('conditions' => array('language_alias' => $id))));
}
}
?>
I only want to see languages on index() that have tongue twisters. How can I do this?
There might be a more efficient way, but here's how to pick only unique languages from the Tonguetwister table:
function index() {
$languageList = $this->Tonguetwister->find(
'list',
array(
'fields' => array( 'language_alias', 'language_alias' ),
'group' => 'Tonguetwister.language_alias',
'recursive' => -1
)
);
// $languageList is now an array that holds the language ids
$this->set(
'languages',
$this->Tonguetwister->Language->find(
'all',
array(
'conditions' => array(
'Language.id' => $languageList
)
)
)
);
}
By the way, you don't need to put Language into $uses. Since they have a relation set you can access the Language model with $this->Tonguetwister->Language.
You don't really need to do two SQL queries for this. If the tables are joined on "language_alias" you can do something like this:
function index() {
$this->Language->recursive = 0;
$this->set('languages', $this->Language->find('all', array(
'conditions' => array($this->Language->alias.'.language_alias' => $this->Tonguetwister->alias.'.language_alias')
));
}
You should just do one query that's going to join the tables properly.