cakePHP, Model Recursion - cakephp

I have a few models, all with appropriately named model files.
$this->Property->PropertyImage->Image->read();
All linked accordingly. Problem is, the recursive model is not able to attach to all the relationships and for some reason is returning AppModel data type when i do a var_dump on $this->PropertyImage. When i do a var_dump($this->Property); i get data type: Property.
What is going on here, what would cause this to happen? Also how can I fix this problem?

Do you hava a PropertyImage model in your application or is it a HABTM association? If you're having a Property which hasAndBelongsToMany Image, you need a pivot table (properties_images) in the database, but to access Image model from the PropertiesController, you'd do $this->Property->Image without anything in between.

Building on what Marko said, if you have a HABTM relationship your best bet is to use a join table, properties_images.
Then rather than doing a Property->PropertyImage->Image, you would just do a Property->Image->read/find().
What I failed to understand about the HABTM relationship is how to filter based on criteria in the related model. E.g., you cannot do this:
$this->Property->Image->find( 'all', array( 'conditions' => array( 'Image.id' => 7 ) ) );
Instead, you have to add the Containable behavior to the Property model, as described in the manual at http://book.cakephp.org/view/474/Containable.

Related

How do I return models related to a related model?

I have 3 models.
Webcast and Tag are associated witha HABTMA association.
Webcast and Host are associated with a hasMany relationship (Webcast has many Host).
When I do a Tag->find I get Tag and Webcast models, however I want to get all 3. How can I go about that?
If your query is using $this->webcast->find then you'll get everything your looking for, except you can't search 'TAG' without joining the tables before the query. If you want to search 'TAG' which I recommend in this situation then your need to go into your Tag model and create relationships there too.
Tag HABTM Webcast
should do it. If you're not getting host then try 'recursive' => 2 in your query.
$this->Tag->find('all');
OR
$this->Tag->find('all', array('recursive' => 2));
From docs
The recursive property defines how deep CakePHP should go to fetch associated model data via find(), and read() methods.
http://book.cakephp.org/2.0/en/models/model-attributes.html#recursive

Choose models in related models

I have a model Post that have a lot of another models relationships.. in some part of the app I want select one post and just bring Comment model. I'm doing this:
$this->Post->find('fist', array('contain'=> 'Comment'));
Its working because is returning just Comment model and not a lot of another related models that i dont want.. the issue is. I want choose the models the Comment model will return, in this case I want the model User that is related to Comment model.
I want my array looks like that:
'Post'
title=> 'Title here'
'Comment'
text=> 'Comment here.. its good etc'
'User'
name=> 'Jason Miller'
The containable behavior can do deep associations. Assuming your Comment model is associated with User already, just do the following:
$this->Post->find('first', array('contain' => array('Comment' => array('User'))));

Retrieving a belongsTo relationship data in a hasOne relationship (deep associations)

I'm building this CakePHP application with a Teaser model, each Teaser has one Location.
Each Location belongs to a Country and a State.
On the index side of Teaser (and view) I want to display the Country.abbr and State.title of the Location associated with this Teaser
Associations
Teaser
public $hasOne = array('Location');
Location
public $belongsTo = array('Teaser','Country','State');
Country
public $hasMany = array('Location');
State
public $hasMany = array('Location');
What have I tried
Default
If I do the following
$teasers = $this->Teaser->find('all');
in my TeasersController then it won't fetch the correct Country and State. I have seen it working and I had no problem to fetch a country title in my view through the Teaser model as follows:
$teaser['Location']['Country']['title'];
$teaser['Location']['State']['title'];
So first this worked, after working on some other stuff I noticed this stopped working (not sure if other stuff had influenced this, but I didn't touched the model. As a good programmer I started looking here.
Containable behaviour
I've read about the Containable behaviour being amazing so I looked up on how to use it and came up with the following.
$this->Teaser->Behaviors->attach('Containable');
$teasers = $this->Teaser->find('all', array(
'contain' => array(
'Location' => array(
'Country',
'State'
),
'User',
),
));
$this->set('teasers', $teasers);
This does not work, it gives the following error:
Model "Location" is not associated with model "Country"
Recursive parameter
I've tried setting recursive to 2 (or -1 in association with the Containable Behaviour) as it was a solution before for others, but I've seen comments on how you better stick with the Containable Behaviour.
Actual question
So, Does anyone has an idea what I'm doing wrong here? Is the syntax wrong or am I getting these associations all mixed up?
Related question
Furthermore a related question on the side I've left out in the main part, but is an association between Country and State a wise idea? Our would this just complicate things even more?
Rather than adding the containable behavior on the fly, I would add the following properties to your AppModel, since using containable should be the default state, rather than the exception.
public $actsAs = array('Containable');
public $recursive = -1;
In case you weren't already aware, the reason people say use containable instead of recursive is that recursive will get everything all the time, even when you don't need it. So recursive is easy to use, but it encourages bad habits.
As for your query, I don't see anything that jumps out at me as being incorrect. I would try, as a test only, doing
$this->Teaser->recursive = 2;
$teasers = $this->Teaser->find('all');
Then see what it comes back with. If it doesn't contain your associated data, you'll know there's something wrong with the associations themselves. If it does get the associated data, then it's something with the query.
As for your related question, that's more of design question which doesn't necessarily have a right answer.

Removing a belongsTo from a model shows warning Model 'x' is not associated with model 'y'

I am new to CakePHP and I am having to maintain/develop a CakePHP application and struggling with removing a BelongsTo in one of the models.
The code has been copied from another project that used a lot more related tables. In the new project I do not need the relationships because it is creating LEFT JOINS in the queries that I really want to get rid of in order to speed things up. So I went in to the model and removed the $belongsTo property..
When I go to the home page I now get a warniing:-
Warning (512): Model "Product" is not associated with model "ParentProduct"
[CORE/Cake/Model/Behavior/ContainableBehavior.php, line 343]
If I turn off debugging it is not shown but I would like to know why this message is being generated. I am unable to find any information on how I should go about removing the belongsTo relationships.
Any help appreciated.
So I went in to the model and removed the $belongsTo property
Because you're asking for it
This kind of error is displayed when using the containable behavior incorrectly.
E.g. Consider User hasMany Article, and the following code:
$User->find('all', array(
'contain' => array(
'Article'
)
));
This would return all users and their articles (only), irrespective of any other associations that exist.
If an association is requested that isn't defined:
$User->find('all', array(
'contain' => array(
'Article',
'Comment'
)
));
Containable will generate a warning so that you, the developer, can know that there will be no Comments in your results which either indicates a typo or another kind of development error (Ah... it's User<-Article<-Comment).
This is in-keeping with what you describe since the model associations have been modified but (evidently) not the application code.
The message is been generated (most probably) because in your controller (check the ParentProductsController first) is something like
$this->ParentProductsController->Product->find();
That association of models no longer exists because you wanted to get rid of it, so there's no way for ParentProducts to call Product. You need to change calls like that to
ClassRegistry::init('the_name_of_the_model');
(in this case ClassRegistry::init('Product');) or load the model with
$this->loadModel('Product');
Now, this only is necessary when you call models not related to the current controller. If you are calling Product->find from the ProductsController, there will be no error.
Ok, that's the cause of the error.
But now: I don't recommend you deleting the association. There's really no need and your removing a logical association in the code even though it's still there in the database. There are other ways to avoid left joins and useless queries to the database without cutting someone a limb.
If you want to speed things up, read about Containable Behavior, and set all models to $recursive = -1 (recursive doc). That way you won't get LEFT JOINS unless you explicitly say so.

How can I minimize the 'contain' queries in CakePHP?

I have three models, Users, Comments and Pages.
Users has many Comments, and Comments belong to Pages.
All models use the containable behavior, and default to recursive -1.
If I call a find() query on Comments, with the contain request including the Page model's field, this correctly returns the results using a single query, automagically joining the Page table to the user.
If I call a similar query from the User model (containing Comment and Comment.Page), the result is a query to source the Comments, followed by a query per comment to source the relevant Page.
Is there a way to configure the models to maintain the JOIN optimisation? I assumed the belongsTo declaration on the related model (Comments) would follow through to the host model (Users).
UPDATE
I should clarify, my question used a simplified version of my actual case study. Although the minimal solution I require would include this initial Model hasMany Model belongsTo Model structure, I am also looking for the solution at one or more additional belongsTo Models down the chain (which, I though, would automagically use LEFT JOINs, as this would be feasible).
Hmm that's interesting. That's a sort of optimization that should be implemented in the core :)
At any rate, I think you could get the same results (perhaps formatted differently) by building the query a little differently:
$this->User->Comment->find('all', array(
'conditions' => array(
'Comment.user_id' => $userId
),
'contain' => array(
'User',
'Page'
)
));
By searching from the Comment model, it should use two left joins to join the data since they are both 1:1 relationships. Note: The results array may look a little different than from when you search from the User model.
So are you asking if there is an easier way to just contain all your queries? If you want to contain everything within the current controller. You could do the contain in the beforeFilter() callback and it would apply to all your queries within that controller.
I am not quite sure if I understand your question, but I think you have a problem with the many sql-calls for the Comment -> Page linkage? If that is correct, then
try linkable behaviour which reduces sql calls and works almost as contain does
or if its pretty much the same data you want, then create a function in a specific model from where you are happy with hte sql calls (for example the Comment-Model) and call it from the user model by $this->Comment->myFindFct($params);
hope that helps
EDIT: one thing that comes to my mind. You were able to change the join type in the association array to inner, which made cake to single call the associated model as well
I find a good way to do this is to create a custom find method.
As a for instance I'd create a method inside your User model say called _findUserComments(). You'd then do all the joins, contains, etc.. inside this method. Then in your controllers, wherever you need to get all of your user's comments you would call it thusly:
$this->User->find('UserComments', array(
"conditions" => array(
'User.id' => $userId
)
));
I hope this helps.
If model definition like bellow:
Comment model belongs to Page and User.
Page belongs to User and has many Comment.
User has many Page and Comment
code bellow will return one joined query:
$this->loadModel('Comment');
$this->Comment->Behaviors->attach('Containable');
$queryResult = $this->Comment->find('all', array(
'contain' => array(
'User',
'Page'
)
));
The code bellow will return two query. Page and User joined into one query and all comment in another query
$this->loadModel('Page');
$this->Page->Behaviors->attach('Containable');
$queryResult = $this->Page->find('all', array(
'contain' => array(
'User',
'Comment'
)
));
and also bellow code will return three query, one for each model:
$this->loadModel('User');
$this->User->Behaviors->attach('Containable');
$queryResult = $this->User->find('all', array(
'contain' => array(
'Page',
'Comment'
)
));

Resources