I'm working on a CakePHP 1.2 application. I have a model "User" defined with a few HABTM relationships with other tables through a join table.
I'm now tasked with finding User information based on the data stored in one of these HABTM tables. Unfortunately, when the query executes, my condition is rejected with an error about a missing table. Upon inspection it seems that CakePHP is not including any of the HABTM tables in the select statement.
My User HABTM relationship is as follows:
var $hasAndBelongsToMany = array(
'Course' => array(
'className' => 'Course',
'joinTable' => 'course_members',
'foreignKey' => 'user_id',
'associationForeignKey' => 'course_id',
'conditions' => '',
'order' => '',
'limit' => '',
'uniq' => false,
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
),
'School' => array(
'className' => 'School',
'joinTable' => 'school_members',
'foreignKey' => 'user_id',
'associationForeignKey' => 'school_id',
'conditions' => '',
'order' => '',
'limit' => '',
'uniq' => false,
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
),
'Team' => array(
'className' => 'Team',
'joinTable' => 'team_members',
'foreignKey' => 'user_id',
'associationForeignKey' => 'team_id',
'conditions' => '',
'order' => '',
'limit' => '',
'uniq' => false,
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
)
);
The error is:
SQL Error: 1054: Unknown column
'School.name' in 'where clause'
And finally, the query it is trying to execute
SELECT
`User`.`id`, `User`.`username`, `User`.`password`, `User`.`firstName`, `User`.`lastName`, `User`.`email
`, `User`.`phone`, `User`.`streetAddress`, `User`.`city`, `User`.`province`, `User`.`country`, `User
`.`postal`, `User`.`userlevel`, `User`.`modified`, `User`.`created`, `User`.`deleted`, `User`.`deleted_date
` FROM `users` AS `User` WHERE `User`.`id` = 6 AND `School`.`name` LIKE '%Test%' LIMIT 1
Turn your debug level up to 2 and look at the SQL output. Find the query that your code is generating and you'll notice there are several. The ORM layer in CakePHP doesn't join HABTM related tables in the first query. It gets the results from the first select, then separately fetches the HABTM data for each item. Because the join table is not in the first query, your condition, which is intended for use on a joined table, results in the error you are seeing.
The cook book has a section on HABTM associations and fetching data based on conditions in the HABTM table that will fit your requirements.
From the Cookbook: http://book.cakephp.org/view/83/hasAndBelongsToMany-HABTM
One option is to search the Tag model (instead of Recipe), which will also give us all of the associated Recipes.
$this->Recipe->Tag->find('all', array(
'conditions' => array('Tag.name' => 'Dessert')));
We could also use the join table model (which CakePHP provides for us), to search for a given ID.
$this->Recipe->bindModel(array('hasOne' => array('RecipesTag')));
$this->Recipe->find('all', array(
'fields' => array('Recipe.*'),
'conditions' => array('RecipesTag.tag_id' => 124) // id of Dessert
));
It's also possible to create an exotic association for the purpose of creating as many joins as necessary to allow filtering, for example:
$this->Recipe->bindModel(array('hasOne' => array('RecipesTag',
'FilterTag' => array(
'className' => 'Tag',
'foreignKey' => false,
'conditions' => array('FilterTag.id = RecipesTag.tag_id')
))));
$this->Recipe->find('all', array(
'fields' => array('Recipe.*'),
'conditions' => array('FilterTag.name' => 'Dessert')
));
Your table should be called "schools_users" and not "school_members" because it's many-to-many, thus using the plural form in the table name on both sides is appropiate.
You also set the Model ClassName "School" as Alias for the HABTM. You should change that to something more generic like "Schools" as in "User is in and has many SchoolS" to avoid conflicts.
And another hint: Try to find the user "via" the School Model rather than the User Model.
$this->User->Schools->find()
Hope this helps.
FWIW, your join tables do appear to be "oddly named" insofar as they don't follow the convention described here:
http://book.cakephp.org/view/83/hasAndBelongsToMany-HABTM
In any case, good luck, I remember HABTM being a butt-pain in CakePHP.
Related
I have jobs and countries table
Table - Jobs
Column name = pickup_region1
Table - Countries
Column name = id
I am using find query on job table..
How to associate country table on job model so that when I run select query on job it automatically bind this relation
jobs.country_id = Countries.id
class Job extends AppModel {
public $belongsTo = array(
'country' => array(
'className' => 'country',
'primaryKey' => 'id',
'foreignKey' => 'country_id',
'dependent' => true,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
) );
}
Model for countries should be: Country.php for table countries,
when you stick to convention, this statment is enought:
public $belongsTo = array('Country');
If you have $recursive set to -1, you need to tell cake which association should use when, like that:
(more about $recursive)
$this->Job->contain(array('Country'));
And next query on Job will arrive with Country
You can also contain inside find query:
$this->Job->find('all', array(
...
'contain' => array('Country')
));
more info
I am new to cake php . Please be cool with me . I am trying to fetch the data from the two table ce_actionitems and ce_projects.
i want to fetch the data from action items . my model is
public $hasOne = array(
'Project' => array(
'className' => 'Project',
'foreignKey' => 'id',
'joinTable' => 'actionitems',
'unique' => 'keepExisting',
'associatedKey' => 'project_id',
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
)
);
but is is returning the null data because it is matching id in projects with id in action items. this is the generated query
LEFT JOIN EB.ce_projects AS Project ON (Project.id =
Actionitems.id)
but i want this to be
LEFT JOIN EB.ce_projects AS Project ON (Project.id =
Actionitems.project_id)
i got this working if i mention public $primaryKey = 'project_id'; in my Actionitems but then it stop inserting the action items.
any help will be appriciated.
mention 'project_id' as foreign key in Actionitems
In a controller which is completely unrelated - a DoctorsController, I need to get some info from my ChildrenMedicine model and also the name of the medicine which is in it's own related Medicine model. So in my (unrelated) DoctorsController controller I use:
$this->loadModel('ChildrenMedicine');
$this->ChildrenMedicine->recursive = 2;
$childsMeds = $this->ChildrenMedicine->find('all', array('conditions'=>array('child_id'=>$child)));
I expected this to get me an array with all the medicines which the child currently uses, plus the info from the medicines table so I can get the name. However it only gives me:
$childsMeds[0]['ChildrenMedicine']
$childsMeds[1]['ChildrenMedicine']
whereas I was hoping for
$childsMeds[0]['ChildrenMedicine']
$childsMeds[0]['Medicine']
$childsMeds[0]['Child']
$childsMeds[1]['ChildrenMedicine']
$childsMeds[1]['Medicine']
$childsMeds[1]['Child']
Can anyone enlighten me?
EDIT - my ChildrenMedicine model associations:
public $belongsTo = array(
'Child' => array(
'className' => 'Child',
'foreignKey' => 'child_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Medicine' => array(
'className' => 'Medicine',
'foreignKey' => 'medicine_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
Use Containable Behavior.
$this->loadModel('ChildrenMedicine');
$this->ChildrenMedicine->Behaviors->load('Containable');
$childsMeds = $this->ChildrenMedicine->find('all',
array(
'conditions' => array('child_id'=>$child),
'contain' => array('Child', 'Medicine')
));
A alternative method would be to use joins to solve this issue. It would be a better option that relying on setting recursive = 2.
$data = $this->ChildrenMedicine->find('all', array('conditions' => array('child_id'=>$child), 'joins' => array(
array(
'table' => 'medicines',
'alias' => 'Medicine',
'type' => 'inner', // could also do outter
'foreignKey' => false,
'conditions'=> array('ChildrenMedicine.medicine_id= Medicine.medicine_id')
))));
You can read more on joins here
For setting up HABTM relationships this article seems helpful. Look at "The Model" code
I have created hasManyThrough table like below:
id user_id friend_id
I also have users table like below:
id username password
I want to have both user_id and friend_id belongsTo relation with user's id column. So I have written below two models:
Friend model
class Friend extends AppModel {
var $name = 'Friend';
//The Associations below have been created with all possible keys, those that are not needed can be removed
var $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Friend' => array(
'className' => 'User',
'foreignKey' => 'friend_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
User model
class User extends AppModel {
var $name = 'User';
var $displayField = 'username';
var $hasMany = array(
'User' => array(
'className' => 'Friend',
'foreignKey' => 'user_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
),
'Friend' => array(
'className' => 'Friend',
'foreignKey' => 'friend_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
);
}
I'm not sure why but when I try to view this via controller in scaffolding, it comes up with 500 server error?? Is there something wrong with the model config that is creating wrong SQL?
OK it seems like error is due to Zend Optimizer according to some forums. This is creating Segmentation Fault when baking!
I cannot turn off this in the third party hosted server so I might have to move to my local server and test so I can see Cake errors.
OK below is the error (it's very long so I'm not going to put all but below gives a bit of an idea):
Fatal error: Maximum function nesting level of '100' reached, aborting! in /usr/share/php/cake/libs/debugger.php on line 248 Call Stack: 0.0011 352048 1. {main}()
Most probably problem is occurring due to nesting of Friend Model construct and which is occurring due to use of same alias of Friend for User and Friend table has well. Here is what happening i guess:
Friend Model being initialized, which in its constructs during model binding initialize User Model.
Now when User Model is initialized, during its binding Process, It Tries to bind Friend Model with alias as User, but due to use of Singleton pattern in Models, Instead of Friend Model instance ClassRegistry returns instance of User, resulting User model binding to itself.
Similarly is the case when User is Initialized first.Change Alias of relations like Friend_2,
or something else, ie Dont repeat Alias in Model relations.
Look at my answer in CakePHP Twitter-clone: Can't get follow-system to work. It`s the same but with friends rather then followers.
You just make a user_users table with a user_id (User ID) field and child_user_id (Friend ID), you can use the user_users table again to make similar relations eg user HABTM followers, user HABTM followers
Sorry for the late response, I thought I posted the answer a day ago but clearly it didn't post.
I'm trying to search based on a field in a related model. I can do so with the belongsTo model, but not the hasMany model. I'm sure this is something simple I'm overlooking but I can't see it. Could someone point me in the right direction (or even the cakebook page I need - searched, but didn't find anything that looked like this). Thanks
In groups controller: (this doesn't work)
$group = $this->Group->find('all', array('conditions' => array('Voucher.id' => '55')));
This does:
$group = $this->Group->find('all', array('conditions' => array('TourOperator.id' => '3')));
Background file snippets:
Group model:
var $belongsTo = array(
'TourOperator' => array(
'className' => 'TourOperator',
'foreignKey' => 'tour_operator_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
var $hasMany = array(
'Voucher' => array(
'className' => 'Voucher',
'foreignKey' => 'group_id',
'dependent' => true,
'conditions' => '',
'fields' => '',
'order' => 'Voucher.date, Voucher.meal_type_id'
)
);
Voucher model:
var $belongsTo = array(
'Group' => array(
'className' => 'Group',
'foreignKey' => 'group_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
Tour Operator model:
var $hasMany = array(
'Group' => array(
'className' => 'Group',
'foreignKey' => 'tour_operator_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
);
Update (as posted in comment below, but clearer to read here)
I've ended up using this.
$groups = $this->Group->Voucher->find('all', array('fields' => 'DISTINCT group_id', 'conditions' => array('Voucher.status' => 'pending')));
$group_ids = Set::extract($groups, '/Voucher/group_id');
$data = $this->Group->find('all', array('conditions' => array('Group.id' => $group_ids)));
I get a distinct list of group IDs matching my criteria, create an array of just the IDs and then use that to pull the groups so that the arrays are ordered as I expect them in my view.
Can you recast your query to $this->Voucher->find, and then use the associated Group data returned from that query? As you say, there's no problem finding groups by their TourOperator id, and the Groups/Voucher relationship looks like it's the inverse of that.
With CakePHP's Containable behaviour you can't use conditions on related data in hasMany and hasAndBelongsToMany associations. This is because it uses seperate queries to get those associations.
With the Linkable behaviour, however, you can use data from hasMany associations to filter records. The behaviour can be downloaded here: https://github.com/Terr/linkable
Linkable uses standard JOINs to, well, join related data. This means it doesn't pull in all the related data (ie, all the Vouchers related to your Group(s)), just like a normal SELECT. However, it can work together with Containable to do both.
You can find examples of how Linkable works in the README.
(disclaimer: I currently maintain Linkable, but that is not the reason I point to it)