How extensive is an Object in CakePHP model linkage? - cakephp

I was hoping someone with an understanding on CakePHP could shed some light on a question I've been having.
Here's my scenario, I have a User this User has a Company which in turn has many Department and many Address. If I were to get a User could I expect to have access to the Company and all models associated with that Company?
So would $user['Company']['Department'][0] or $user['Company']['Address'][0] be possible?
Which brings me back to the original question, how extensive is the linkage between models?

In plain-vanilla model, Cake's model linkage is determined by your models' recursive attribute. Your example model relationship looks something like this:
User
-> belongsTo Company
-> hasMany Department
-> hasMany Address
-> hasMany PhoneExtension
(I've added an additional relationship (User hasMany PhoneExtension) to flesh out the following explanation.)
There are three accepted values for Model::recursive: -1, 0, 1 and 2. Each value indicates to Cake's ORM a different depth to retrieve model records. I'll use $this->User->find('all') to illustrate the difference.
At recursive = -1, Cake retrieves only the specified model (ie. User). It parses none of the model associations.
At recursive = 0, Cake retrieves the specified model, and parses its belongsTo associations. $this->User->find('all') would retrieve all User records, as well as the Company record to which each User belongs.
At recursive = 1, Cake retrieves the specified model, and parses all of its direct associations. $this->User->find('all') would retrieve all User records, as well as the Company record to which each User belongs, and all PhoneExtension records belonging to the User.
At recursive = 2, Cake retrieves the specified model, parses all of its direct associations and all associations of its direct associations. $this->User->find('all') would retrieve everything in the example model relationship diagram: all User records, the Company records to which the User records belong, all PhoneExtension records belonging to the User, and all Department and Address records belonging to the Company.
Which is the very long way of saying that yes, you can achieve the results you indicate in your question, at recursive = 2.
If you wanted to go deeper than what recursive = 2 gets you, you'll have to use the Containable behaviour. Let's say that your Department model had an additional association: hasMany Group. Thus:
User
-> belongsTo Company
-> hasMany Department
-> hasMany Group
-> hasMany Address
-> hasMany PhoneExtension
To retrieve everything we got with a recursive = 2 retrieval, as well as all the associated Group records, you'd construct your Model::find call like this:
$this->User->find('all', array(
'contain' => array(
'PhoneExtension',
'Company' => array(
'Department' => array( 'Group' ),
'Address'
)
)
));

It's as extensive as you need/want it to be. Look into the recursive option of the find() family of methods. Also the Containable behavior. The specific references you list are possible, but directly under the user:
$user['Department'][0]
Think of it as the user having many departments through its company.

If you access a class/object and set "$this->recursive = -1" then it only returns the object without the dependencies!

Related

CakePHP Models with associated conditions

I have three models that are as follows:
Location hasMany SportType hasMany Sport, then
Sport belongsTo SportType belongsTo Location
In the SportType model, the belongsTo Location has a condition 'Location.status' => true, such that it only retrieves records where the Location status is true. Works fine.
When retrieving records via a plain old find() in the Sport model, I would assume that it would not return records where the associated SportType's associated Location was false, but that is not the case.
I believe I could use containable behavior or explicitly constructed joins in my controller to get what I want, but I'm wondering if this is something I can achieve purely through the model relationships. Perhaps not.
You can either use Joins or change the model you're doing the search on and do it through the restricting model (ie Location).
$this->Location->find('all', array(
'conditions' => array(
'Location.status' => true
),
'contain' => array(
'SportType' => array(
'Sport'
)
)
));
But you cannot narrow the results of the searching model based on conditions within contained models.
Update:
Joins also allow you to add more conditions to other models...etc, as opposed to Contain which does not, so I supposed I'd lean toward going with Joins as that leaves you more flexibility moving forward.
Also, a JOIN will do one more-complicated query, while a contain will do many simpler queries... so depending on your database structure, that could be considered.
Bottom line, it's preference - both are just fine and whichever works for you is great.

CakePHP 2.1 HABTM Not Getting Associated Info

I have a question that's been bugging me all afternoon:
I'm making a guitar gear site, so I am using a gear items table, a user table, and gear-to-user bridge table. That is, a user can own multiple items and items can belong to multiple users. So HABTM.
An item belongs to a brand (e.g. Fender - Stratocaster), so I set up a belongsTo relationship in the item model as well as a HasMany relationship in the brands model. When I check the output in the items controller, the gear and its associated brand's data is all there as it should be.
The user control panel (and similar areas) basically list all of the user's owned items. After setting up the HABTM relationship between users and items, I checked the controller's output. While the item's information and the bridge table information all appeared, the item's associated brand information did not. The results should essentially be a list of items, including brand information, as if it were "where user_id = x". Instead, it seems to only be grabbing the item information and none of its relationships.
Is there something I'm missing or a dumb mistake? Thanks.
Did you consider setting/changing the recursive attribute when performing find() on the User?
The higher recursive is set, the deeper associations will be found.
An alternative might be to use the Containable behavior on User:
$this->User->find('first',array(
'conditions'=>array('User.id'=>1),
'contain' => array('Item'=>array('Brand'))
));
Set the recursive level up or you can use ContainableBehavior

CakePHP : Multiple hasOne Model Relations

I'm trying to create an application which should help distribute work to employees or volunteers on a irregular time schedule based on their availabilities.
Anyway, here are my models and the relations between them :
Users hasMany Jobs
Jobs belongTo Users
Users hasMany Availabilities
Availabilities belongTo Users
Jobs hasOne Periods
Periods belongTo Jobs
Availabilities hasOne Periods
Periods belongTo Availabilities
The problem is that "Periods" is on the receiving side of two hasOne relationships and, if I'm not wrong, it's something you can't do in CakePHP. What is the best way to proceed in this situation?
You might have also spotted the fact a "Job" may be assigned to someone (a "User") or not. Should I drop the relationship and create it once the job has been assigned or should I create a fictional user representing "nobody" for unassigned jobs?
I would have posted a nice image but i don't have enough reputation, sorry!
Here's a link though.
The problem is that "Periods" is on the receiving side of two hasOne relationships and, if I'm not wrong, it's something you can't do in CakePHP. What is the best way to proceed in this situation?
I don't see a problem. You should be able to add the foreign key for each hasOne relationship to the periods table (periods.job_id and periods.availability_id).
You might have also spotted the fact a "Job" may be assigned to someone (a "User") or not. Should I drop the relationship and create it once the job has been assigned or should I create a fictional user representing "nobody" for unassigned jobs?
This is fine. Just set the jobs.user_id to allow null values and set up the relationship. This will allow jobs to be created without being assigned to users. After you perform a find you can check empty($results['User']) to determine if you are dealing with an unassigned job.
Users hasMany Jobs, Jobs belongTo Users, ...
Just a reminder: model names should be singular (ie. User hasMany Job, Job belongsTo User, etc.)
Also I'd like a Period to belong to either a Job or an Availability and never both at the same time. Is there a conventional way to do it?
You can write validation for that in the Period model:
$validate = array(
'job_id' => array(
array(
'rule' => 'validBelongsTo',
'message' => 'A period can either belong to a job or an availability'
),
),
);
public function validBelongsTo() {
$hasJob = !empty($this->data['Period']['job_id']);
$hasAvailability = !empty($this->data['Period']['availability_id']);
if ($hasJob && $hasAvailability) {
return false;
} else {
return true;
}
}
Sounds to me like the Period model should be polymorphic. Check out the answer to this question and this article in the bakery.
I used this technique frequently and it works beautifully for this kind of thing.

Managing categories for several models in CakePHP

I have a bunch of models that I'm going to be creating, and these models will have different types of categories that they can belong to. For example, one model will HABTM one set of categories, another model will HABTM another set.
I've come up with one idea - creating a model called Category, and having a categories table with a 'model' field, that contains the name of the model that this category has a HABTM relationship with. Then the other models would have something like this:
public $hasAndBelongsToMany = array(
'Category'=>array(
'conditions'=>array(
'Category.model'=>'Modelname'
)
)
)
This seems OK but
this code will be repeated in every model which seems silly and
some categories will apply to several models, so there would be duplicate database entries for those categories.
Is there a better way? Thanks for your help!
You can use one category model and add a model field to the category model to specify the related model. So you can use different models with one category model... Like you said...
I think this is a good idea.

CakePHP hasAndBelongsToMany (HABTM) Delete Joining Record

I have a HABTM relationship between Users and Locations. Both Models have the appropriate $hasAndBelongsToMany variable set.
When I managing User Locations, I want to delete the association between the User and Location, but not the Location. Clearly this Location could belong to other users. I would expect the following code to delete just the join table record provided the HABTM associations, but it deleted both records.
$this->Weather->deleteAll(array('Weather.id' => $this->data['weather_ids'], false);
However, I am new to CakePHP, so I am sure I am missing something. I have tried setting cascade to false and changing the Model order with User, User->Weather, Weather->User. No luck.
Thanks in advance for any help.
Not quite sure how Weather is related to your models, so I'll just go with the traditional names, LocationsUser is the joining table. This should delete all associations between the user with id $id and any locations:
$this->User->LocationsUser->deleteAll(array('LocationsUser.user_id' => $id), false);
Notice also that you're missing a closing bracket in your code snippet.

Resources