CakePHP Setting recursive for select joins - cakephp

I create a search on models like this:
$options = array(
'conditions' => array(
'CompletedSurvey.' . $this->CompletedSurvey->primaryKey => $id
),
'recursive' => 5
);
$survey = $this->CompletedSurvey->find('first', $options);
The way I have my models set up, this will return five models (do to their various joins) each recursed up to 5 times (if available). The problem is that I only want one of these models to be recursed X5. The others don't need to be.
Is there a way to tell the find function which tables to recurse and at what level to recurse them to? So, telling cake which models to recurse and at what level for each one?

Please read the CakePHP cookbook regarding the recursive property on models, as it does not work the way you are thinking. http://book.cakephp.org/2.0/en/models/model-attributes.html#recursive
What you're looking for is the containable behavior, where you can specify exactly which models to return. Please see the cookbook on how to use containable. http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html

Well, this is not exactly recursive, but the only way to access deep relations and avoid to use recursive on a main model is to use containable behavior:
$options = array(
'conditions' => array(
'CompletedSurvey.' . $this->CompletedSurvey->primaryKey => $id
),
'contain' => array(
'SomeModel.SomeOtherModel.AnotherModel.AnotherModel'
)
);
$survey = $this->CompletedSurvey->find('first', $options);
And don't forget to set containable behavior for CompletedSurvey model!
class CompletedSurvey extends AppModel {
public $actsAs = array('Containable');
}

Related

Multi level ordering in cakePHP

Why don't cakePHP include all my associations into one SQL query? The only way I have been able to do this is using "joins", but I hoped belongsTo and Containable behaviour was enough.
Here is an example:
Post->belongsTo->Category->belongsTo->category_type
(All models are setup correctly and work.
listing posts with pagination in index, I try this:
public function index() {
$this->paginate = array( 'contain' => array('Category' => array('CategoryType')));
$this->set('posts', $this->paginate());
}
This fetches the array correctly, but it does it in many SQLs like this:
SELECT `Post`.`id`, `Post`.`name`, `Post`.`content`, `Post`.`category_id`, `Category`.`id`, `Category`.`name`, `Category`.`category_type_id` FROM `unit_app`.`post` AS `Post` LEFT JOIN `unit_app`.`categories` AS `Category` ON (`Post`.`category_id` = `Category`.`id`) WHERE 1 = 1 LIMIT 20
SELECT `CategoryType`.`id`, `CategoryType`.`name` FROM `unit_app`.`category_types` AS `CategoryType` WHERE `CategoryType`.`id` = 1
SELECT `CategoryType`.`id`, `CategoryType`.`name` FROM `unit_app`.`category_types` AS `CategoryType` WHERE `CategoryType`.`id` = 2
SELECT `CategoryType`.`id`, `CategoryType`.`name` FROM `unit_app`.`category_types` AS `CategoryType` WHERE `CategoryType`.`id` = 2
This makes it difficult to order this query on CategoryType.name ASC.
Any suggestions?
If joins are only option, do I have to unbind the models before querying?
Will pagination work fine with joins?
Note! this is just a small part of all models, the resulting post->index need to fetch many other models through similar associations also.
(tested on cake 2.2.0 and v2.4.0-dev, php v5.4.11)
UPDATE! ---------
I just wanted to show my findings. I have now solved this without joins, but I had to re-bind in the model to get it working.
This is basically what I did to get it to work (also with paginations and sorts):
In Post model:
Added a bind function:
$this->unbindModel(array(
'belongsTo' => array('Category')
));
$this->bindModel(array(
'hasOne' => array(
'Category' => array(
'foreignKey' => false,
'conditions' => array('Category.id = Post.category_id')
),
'CategoryType' => array(
'foreignKey' => false,
'conditions' => array('CategoryType.id = Category.category_type_id')
))));
Then I added this to my index in Post controller:
$this->Post->bindCategory();
$this->paginate = array('contain' => array('Category' ,'CategoryType'));
$this->set('posts', $this->paginate());
I include table headers also just for documentation:
<th><?php echo $this->Paginator->sort('CategoryType.name', 'Type'); ?></th>
<th><?php echo $this->Paginator->sort('Category.name', 'Category'); ?></th>
I hope this post can help others as well :)
I am also going to test this Behaviour to see if I can omit all the bind-functions as well: https://github.com/siran/linkable/
There are lots of plugins to cake, but cake should have a "certification" of the plugins. It is quite difficult to find the fully working and tested ones on github :)
I also miss a site like railscasts.com just for cake :D
/MartOn
Yes, you must use JOINs to be able to order based on an associated models results.
Yes, you can paginate with JOINs. Just pass your options (including JOINs) to your paginate prior to actually calling $this->paginate();. (there are many resources online for how to paginate with JOINs)

Cakephp complex hasMany relationship query

I'm very new to CakePHP. I want to do a query in my database like this
SELECT m.id, l.*, lp.picture_path
FROM member m INNER JOIN listing l ON m.member_id = l.member_id
INNER JOIN listingPicture lp ON l.listing_id = lp.listing_id
WHERE lp.picture_default='1'
I have 3 models in my cakephp : Member, Listing, and ListingPicture each with the following relationship
Member hasMany Listing
Listing hasMany ListingPicture
Listing belongsTo Member
ListingPicture belongsTo Listing
From my Member controller how do I execute the query above ?
I've tried
$this->Member->Listing->find("all")
... which works well but when I added a conditions like this:
$this->Member->Listing->find('all', array(
'conditions' => array('ListingPicture.picture_default'=>'1')));
... I get an error.
Because I'm new to CakePHP, I don't know how to see the error.
Can anyone advise me how I can perform this query?
Make sure to set your model as:
public $actsAs = array('Containable');
Then use CakePHP's containable behavior to include only the associated data you want, with specified fields and conditions.
$this->Member->Listing->find('all', array(
'fields' => array('*'),
'contain' => array(
'Member' => array(
'fields' => array('id')
)
'ListingPicture' => array(
'conditions' => array('ListingPicture.picture_default' => '1')
'fields' => array('picture_path')
)
)
));
To follow with the MVC concept, it's suggested to keep your finds in a Model as opposed to a controller. It's not required, but - it makes it much easier to know exactly where all finds are, and keeps with the "Fat model / Skinny controler" mantra. In this case, it'd be something like:
//in the Member Controller
$listings = $this->Member->Listing->getListings();
//in the Listing Model
function getListings() {
$listings = $this->find('all', ...
return $listings;
}
You should give the condition at the time of binding ListPicture to Listing.
$this->Member->Listing->bindModel(array(
'ListPicture'=>array(
'condtions'=>array('ListingPicture.picture_default'=>'1')
))
);

cakephp: find statement with 'contain'

the following User model function is from MilesJones forum plugin. Can someone tell me on what is the use of 'contain' in the find stmt. I couldn't find any example with contain in the cakephp cookbook. Any helps is appreciated.
public function getProfile($id) {
return $this->find('first', array(
'conditions' => array('User.id' => $id),
'contain' => array(
'Access' => array('AccessLevel'),
'Moderator' => array('ForumCategory')
)
));
}
By default when a find statement executes cake pulls all the data from the model on which the find function is executing plus all the data from the models that are associated with the model. Most of the time you don't need that extra data, Cake has containable behaviour for exactly that purpose. You can specify which associated model's data you want in your result.
In the above example find statement will fetch the first record from the User model plus associated data from Access and Moderator models.
Here is the link from cakephp book http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html
Here is cakephp documentation about contain

Pagination with recursion while using loadModel

In my "Reports" controller, which is just a dummy controller without any actual database, I'm trying to generate a paginated view of other models. For example, to generate paginated view of "Transactions" model I'm doing the following:
$this->loadModel('Transactions');
$this->Transactions->bindModel(array('belongsTo'=>array('Item'=>array('className'=>'Item'),'Member'=>array('className'=>'Member'))));
$results = $this->paginate('Transactions',null,array('recursive'=>1));
But this is not giving me associated data from Items and Members. If I do a
$this->Transactions->find('all',array('recursive'=>1))
I get the associated data, but not paginated. How will I get paginated view which includes the associated data too?
Two things: First, even when plural model names can work for some odd reason, the convention is that model names are singular, like $this->loadModel('Transaction');. See the manual on naming conventions.
Second, forget about recursive and go for the Containable behavior. Frankly, it's so useful that I wonder why it isn't the default process (perhaps because Containable got created when the framework was very mature). Matt has a good book explaining why Containable is good (download it, really, it's almost mandatory :D ). But to help even more, I'm going to tell you exactly how you solve your issue with containable:
1) Define the associations in the models, like:
In Transaction model:
var $belongsTo = array(
'Item' => array(
'className' => 'Item',
'foreignKey' => 'item_id',
)
);
In Item model:
var $hasMany = array(
'Transaction' => array(
'className' => 'Transaction',
'foreignKey' => 'item_id',
'dependent' => true,
'exclusive' => true,
)
);
Do the same for the Member model.
2) Create an app_model.php file in /app/ with this code:
(The $actsAs variable here within the AppModel class tells all models to use Containable)
<?php
class AppModel extends Model {
var $recursive = -1;
var $actsAs = array('Containable');
}
?>
3) In the Reports Controller, change the code to something like this:
(The contain parameter is an array of all the associated models that you want to include. You can include only one assoc. model, or all, or whatever you want).
$this->loadModel('Transaction');
$this->paginate = array('Transaction' => array('contain' => array('Item', 'Member')));
$results = $this->paginate('Transaction');
And that's it!

CakePHP find() not working accross models

I am having a very curious problem. I am trying to do a find with conditions that work across model relationships. To wit...
$this->Model->find('first', array(
'conditions' => array(
'Model.col1' => 'value',
'RelatedModel.col2' => 'value2')));
...assuming that Model has a hasMany relationship to RelatedModel. This particular find bombs out with the following error message:
Warning (512): SQL Error: 1054: Unknown column 'RelatedModel.col2' in 'where clause' [CORE/cake/libs/model/datasources/dbo_source.php, line 525]
Looking at the SELECT being made, I quickly noticed that the comparison in the related model was in fact being placed in the WHERE clause, but for some reason, the only thing in the FROM clause was Model, with no sign of RelatedModel. If I remove the comparison that uses the relationship, related models ARE pulled in the result.
I'm using Cake 1.2.4. At first glance, there's nothing in the 1.2.4 -> 1.2.5 changelog that I see that covers this, and you would think that such an obvious bug would be hunted down and fixed a few days later, as opposed to waiting a full month and not mentioning anything in the release annoucement.
So, uh, what's going on?
If your models are using the Containable behavior, make sure you contain those models.
First, in your {model_name}.php file:
class {ModelName} extends AppModel {
var $actsAs = array('Containable');
}
Then in your find:
$results = $this->Model->find('first', array(
'conditions' => array(
'Model.col1' => 'value',
'RelatedModel.col2' => 'value2',
),
'contain' => array('RelatedModel'),
));
If not using Containable behavior, then try explicitly increasing the recursion level:
$results = $this->Model->find('first', array(
'conditions' => array(
'Model.col1' => 'value',
'RelatedModel.col2' => 'value2',
),
'recursive' => 1,
));
Note that the latter method will more than likely retrieve a lot of unnecessary data, slowing down your application's speed. As such, I highly recommend implementing the use of the Containable behavior.

Resources