CakePHP find items with hasOne Model relation that have no associated item - cakephp

I have a user model which has a hasOne relation with a profile model. I want to find the users which have no profile associated with them. I don't know whether the counterCache works for hasOne relation or there is a better way with appropriate conditions.

I believe that the hasOne relationship creates a LEFT JOIN in the SQL statement. Arguably, the generated SQL for your find would look something like (assume there are more columns though):
SELECT User.id, User.name, Profile.id
FROM users AS User
LEFT JOIN profiles AS Profile
ON Profile.user_id = User.id;
All you need to do is add another WHERE clause: WHERE Profile.id IS NULL for unmatched rows. Thus changing your Cakephp code to
$this->User->find('all',array(
'conditions' => array('Profile.id' => 'NULL')))

Related

Cake php model relationships difficulty, esp. hasMany Through

I apologize in advance... I'm a complete noob to cakephp. As such, I'm having great difficulty in getting my models related correctly. To be more specific, I'm having the most trouble with the "hasMany Through" relationship. I've been beating my head against the wall, and scouring the cookbook, but haven't found it as clear as i had hoped.
I'm currently writing a web app with many complex relationships. I'll start with stripped down versions of my tables.
Users:
id, name
Groups:
id, name, department_id
Departments:
id, name, district_id
Districts:
id, name
Roles
id, name
Note that roles are what I call my levels of access(user, admin, superuser).
groups_users
-group_id, user_id
departments_users:
department_id, user_id, role_id
RELATIONSHIPS:
User hasAndBelongsToMany Department
Department hasAndBelongsToMany User
User hasAndBelongsToMany Group
Group hasAndBelongsToMany User
Department hasMany Group
Group belongsTo Department
District hasMany Department
Department belongsTo District
Now, here's where i get a bit more confused.
Since users can be in multiple departments, in multiple districts, as well as have varying roles within each department (e.g. Department admin in DEPT A of DISTRICT A, and District Admin in DEPT B in DISTRICT B), I chose to place the user's level of access in the departments_users table. Since I'm storing more than foreign keys for the named models, ie role_id, this seems like a candidate for the hasMany through relationship, but cake's docs also mention a keepExisting option for the model relationship.
so i tried this, using the example from the cake site. leaving all of the initial datatables and relationships in place, I added a DepartmentPosition model and controller, like the following:
user.php
public $hasMany = array('DepartmentPosition');
department.php
public $hasMany = array('DepartmentPosition');
department_position.php
public $belongsTo = array('User','Department');
I also added a new data table:
department_position:
id, department_id, user_id, role_id
OK, so what i got, likely mistakenly from the cake docs, is that when I add a user to a department, i can include a role_id, and that will all be saved in the department_position data table, if I do saveAll().
I'm not sure if that is at all the right thing to do. I feel more confused now, having written this out, than i did before. lol. I'm very confused as to how to get these relationships correctly set-up. And, still, once I do, I feel i'll have trouble accessing data from associated models. I'm getting errors galore and am unable to retrieve much associated model data. eek. like I said, i'm not the strongest cake person.
Any suggestions, before i have an aneurysm?! lol.
Thanks loads. Any help will be a lot of help!
If a User can have different Role in different Group something is needed in between, HABTM or hasManyThrough table.
Before Cake version 2.1, it was not possible to have HABTM relation with extra colums, (here below role_id). The column would get lost when saving. This is not the case anymore if you set the unique param to keepExisting, but I prefer the hasManyThrough relation over HABTM. I guess it's a question of taste.
User hasMany GroupRole
GroupRole belongsTo User, GroupRole belongsTo Groups, GroupRole belongsTo Role (GroupRole is hasManyThrough table)
Group hasMany GroupRole, Group belongsTo Department
Department hasMany Group, Department belongsTo District
District hasMany Department

CakePHP: hasMany with join table

I have two models: Store and Review. Users of my site can leave a review on a store. In my database, I have a join table, reviews_stores that associates a review with a store.
How do I link my models? I assume a Store should haveMany Review, and a Review should belong to a Store, but the join table is causing issues with CakePHP as CakePHP is assuming my reviews table has a column called store_id.
The reason I'm using a join table is because many parts of my site can be reviewed. For example, brands. A new Review record will be created and a record will be inserted into a brands_reviews table to associate the two records.
Any help would be much appreciated.
Why are you not simply using one Review model and a reviews table with a field "foreign_key" and another field "model"? By this you do not need to duplicate tables and inherit or duplicate models. This will also remove the need for a join table.
If you want to continue to use your db design then you'll have to use a HABTM association over the hasMany association. But even in the case you want to keep that jointable, again, you can use the foreign_key/model and simply have one join table and one reviews table.
By the way, your join table review_store does not follow the conventions, it should be reviews_stores. But then it differs to the schema you've used for brands_reviews. ;)
Seems to me it isn't a many-many relationship but a grouped 1-many relationship. Id lose the join tables and simply have an extra table outlining which 'group' the review belongs to. So the review table would have review_id, link_id(the foreign key for the relevant brand or store), review_type_id(foreign key depicting whether the review is for a brand or store and so on). Then the review_type table only needs to have review_type_id, review_type(varchar).
There's no need for all the join tables for each model you can review, simple store the model name itself in a field in the Review table.
Setup your relationship like so:
class Store extends AppModel {
public $hasMany = array(
'Review' => array('conditions' => array('Review.model' => 'Store')
);
}
You can then do this with as many extra models as you like without having to touch the database.

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.

How extensive is an Object in CakePHP model linkage?

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!

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