Is there a way in cakephp using ORM to get the item that belongs to a specific child item. For example I was to get the related Post record for a specific Comment records.
This is my Comment model:
var $belongsTo = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'post_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
I was trying this but it's pull back every post, even those that don't have the comment I'm querying against:
$this->Post->contain('Comment');
$results = $this->Post->find('all', array(
'contain' => array(
'Comment' => array(
'conditions' => array(
'id' => 15
)
)
)));
Any other way to do this?
Are you sure you don't have to specify the model in your conditions?
For example:
$this->Post->contain('Comment');
$results = $this->Post->find('all', array(
'contain' => array(
'Comment' => array(
'conditions' => array(
'Comment.id' => 15
)
)
)));
my research led me to this post regarding the issue with contains:
http://nuts-and-bolts-of-cakephp.com/2008/07/17/forcing-an-sql-join-in-cakephp/
so my final solution was as follows:
$this->Post->unbindModel(array('hasMany' => array('Comment')));
$results = $this->Post->bindModel(array('hasOne' => array(
'Comment' => array(
'foreignKey' => false,
'conditions' => array('Comment.post_id = Post.id'))
)));
$results = $this->Post->find('all', array(
'conditions' => array(
'Comment.id' => 10
)));
not pretty but gets the job done :)
Related
I have the following code that gives an error from the moment I do a find condition on the associated Model (HABTM):
class Users extends Model{
public $useTable = 'users';
public $hasAndBelongsToMany = array(
'UserCategories' => array(
'className' => 'UserCategories',
'joinTable' => 'user_categories_link',
'foreignKey' => 'user_id',
'associationForeignKey' => 'category_id',
'unique' => false,
)
);
public function getData($page = 0, $category = '', $subcategory = ''){
return $this->find('all', array(
'limit' => 6,
'page' => $page,
'conditions'=> array(
'active'=> 1,
'UserCategories.category_name' => $category, // THIS GIVES ERROR
'UserCategories.category_subcategory' => $subcategory, // THIS GIVES ERROR
)
));
}
In my Controller:
$this->Users->getData(0, 'somemaincategory', 'somesubcategory');
I can't seem to do conditions on the related HABTM-Model (UserCategories in this case). I also tried to use 'contain' (with $actsAs), but then he stills gives me all the User data even if there is no Category linked with it. The Category array is in that case just blank.
Hope someone can help me.
Thanks in advance,
Aäron
Do a manual join. You can use this to do an actual inner join (contain will act as a left join). http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#joining-tables
$this->find('all',
array(
'conditions' => array(
'active' => 1,
),
'joins' => array(
array(
'table' => 'user_categories_link',
'alias' => 'UserCategoriesLink',
'type' => 'inner',
'conditions' => array(
'UserCategoriesLink.user_id = User.id'
),
),
array(
'table' => 'user_categories',
'alias' => 'UserCategories',
'type' => 'inner',
'conditions' => array(
'UserCategories.id = UserCategoriesLink.category_id',
'UserCategories.category_name' => $category,
'UserCategories.category_subcategory' => $subcategory,
),
)
),
)
);
To stay simple, I have a User model which hasMany Post.
[User] id
[Post] id, user_id
How can I write a $this->User->find('all', array('conditions' => $conditions) to get all User who has no Post ?
assuming Post belongsTo User:
$this->User->Post->find(
'all',
array(
'fields' => array('User.id', 'user_id', 'COUNT(user_id) AS posts_count'),
'group' => 'user_id HAVING posts_count = 0'
)
)
Create one function inside User model and try below code: It will give all the User.id having no posts.
$options = array(
'conditions'=>array('Post.id is NULL')
'joins' => array(
array(
'alias' => 'Post',
'table' => ‘posts’,
'type' => 'LEFT',
'conditions' => array(
'Post.user_id = User.id',
),
)
),
'fields' => array('User.id')
);
$returnData = $this->find('list',$options);
I am using cakephp 2.1 and defined 3 tables as follows.
`industries(id, name);`
`movies(id, name, industry_id),`
`trailers(id, name, movie_id);`
I want to paginate the trailers for particular industry. So the code I have written is below:
$this->paginate = array(
'Industry' => array(
'contain' => array(
'Movie' => array(
'order' => 'Movie.release DESC',
'Trailer' => array(
'conditions' => array(
'Trailer.id !=' => $trailer_id
)
)
)
),
'conditions' => array(
'Industry.id' => $id
)
)
);
If I would specify 'limit' for 'Trailer', I get correct number of results but some null array for a movie which doesn't contain any trailers. Please help me to get the number of specified limit of trailers for particular industry. The work will be most appreciated.
It looks to me like you've got your contain key in the wrong place. It should be defined before the models.
<?php
$this->paginate = array(
'contain' => array(
'Industry' => array(
'Movie' => array(
'order' => 'Movie.release DESC',
'Trailer' => array(
'conditions' => array(
'Trailer.id !=' => $trailer_id
)
)
)
)
),
'conditions' => array(
'Industry.id' => $id
)
);
I have the following relationships set up:
Teacher:
var $hasAndBelongsToMany = array(
'Classroom' => array(
'className' => 'Classroom',
'joinTable' => 'classrooms_teachers',
'foreignKey' => 'teacher_id',
'associationForeignKey' => 'classroom_id',
'unique' => true,
)
);
Classroom:
var $hasAndBelongsToMany = array(
'Teacher' => array(
'className' => 'Teacher',
'joinTable' => 'classrooms_teachers',
'foreignKey' => 'classroom_id',
'associationForeignKey' => 'teacher_id',
'unique' => true,
)
);
var $hasMany = array(
'Student' => array(
'className' => 'Student',
'foreignKey' => 'classroom_id',
'dependent' => false,
),
);
Student:
var $belongsTo = array(
'Classroom' => array(
'className' => 'Classroom',
'foreignKey' => 'classroom_id',
),
);
I am trying to create a dashboard for the teachers, where all students associated with the teacher through the classroom are displayed.
I am using the following find operation:
$students = $this->Teacher->Classroom->find('all', array(
'conditions' => array('Classroom.teacher_id' => $this->Access->getTeacherId()),
));
However, I am getting an error: Unknown column 'Classroom.teacher_id' in 'where clause'
I must be doing something wrong because cake is not doing the associations.
Any ideas?
This is the correct way of achieving this:
$this->Teacher->bindModel(array('hasOne' => array('ClassroomsTeachers')));
$students = $this->Teacher->find('all', array(
'conditions' => array('ClassroomsTeachers.teacher_id' => $this->Access->getTeacherId()),
'recursive' => 2
));
I hope it helps someone!
That's not the best way. Just use containable and find('first'), you don't need find all:
$students = $this->Teacher->find('first', array(
'conditions' => array('Teacher.id' => $this->Access->getTeacherId()),
'contain' => array('Classroom'=>array('Student'))
));
That will get you all the classrooms associated with the teacher, and all students in those classrooms.
I made a behavior to solve the HABTM problem.
Posted my answer here:
HABTM Find with CakePHP 2.0
I have the following tables: binders, docs, users, docs_users. Doc belongsTo Binder, Doc hasAndBelongsToMany User.
I want to get binders and their associated docs for the user that is currently logged in (the associated user_id in the docs_users table).
I have tried Containable and find('all') with joins, conditions, etc. but I can't figure out how to remove the Docs that are from Users who are not associated in the docs_users table.
This code does NOT work:
$binders = $this->Binder->find(
'all',
array(
'joins' => array(
array(
'table' => 'binders_users',
'alias' => 'BindersUser',
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array(
'BindersUser.binder_id = Binder.id',
'BindersUser.user_id = ' . $this->Auth->user('id')
)
),
array(
'table' => 'docs',
'alias' => 'Doc',
'type' => 'left',
'foreignKey' => false,
'conditions'=> array(
'Doc.binder_id = Binder.id',
)
),
array(
'table' => 'docs_users',
'alias' => 'DocsUser',
'type' => 'left',
'foreignKey' => false,
'conditions'=> array(
'DocsUser.doc_id = Doc.id',
'DocsUser.user_id = ' . $this->Auth->user('id')
)
)
),
'recursive'=>0
)
);
$this->set('binders', $binders);
And neither does this:
$this->Binder->recursive = 2;
$this->Binder->Behaviors->attach('Containable');
$this->Binder->contain(array(
'Branch',
'Doc' => array(
'User' => array(
'DocsUser' => array(
'conditions' => array('id = "17"')
)
)
)
));
$binders = $this->Binder->find('all');
Any help from you seasoned pros would be great! Thanks!
Alternative/Simplified Solutions?
This works if I just want to get binders to which users have permissions. Short and sweet. However, it will still send ALL associated docs through, which is NOT the behavior I want. It needs to only pass on the docs to which the user has permissions (as described previously).
$binders = $this->Binder->find(
'all',
array(
'joins' => array(
array(
'table' => 'binders_users',
'alias' => 'BindersUser',
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array(
'BindersUser.binder_id = Binder.id',
'BindersUser.user_id = ' . $this->Auth->user('id')
)
)
)
)
);
These are a few of the options available for doing deep finds on data in CakePHP:
https://github.com/Terr/linkable/wiki
http://mark-story.com/posts/view/using-bindmodel-to-get-to-deep-relations
http://bakery.cakephp.org/articles/view/quick-tip-doing-ad-hoc-joins-in-model-find
Here's the final solution I came up with based on all of the great feedback I got. I think this is an elegant solution that can be reused in any scenario where deep associations are required.
In the binder_controller I unbound the Doc model, and bound it back using the finderQuery to select only the Docs that a user has permission to see. Then in joined the binders_users table selecting only the binders that users have permissions to.
Thank you everyone for all your help!
$this->Binder->unbindModel(array('hasMany' => array('Doc')));
$this->Binder->bindModel(
array('hasMany' => array(
'Doc' => array(
'className' => 'Doc',
'foreignKey' => 'binder_id',
'dependent' => false,
'finderQuery' => 'SELECT Doc.* FROM docs AS Doc INNER JOIN docs_users AS DocsUser ON DocsUser.doc_id = Doc.id AND DocsUser.user_id = ' . $this->Auth->user('id')
)
)
)
);
$binders = $this->Binder->find(
'all',
array(
'joins' => array(
array(
'table' => 'binders_users',
'alias' => 'BindersUser',
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array(
'BindersUser.binder_id = Binder.id',
'BindersUser.user_id = ' . $this->Auth->user('id')
)
)
)
)
);
More on binding/unbinding models
On this line, you need to tell Cake which Model's id you are talking about:
'conditions' => array('id = "17"')
e.g. DocsUser.id
...and you don't use recursive with containable. Get rid of it.
Have you tried coming in from a user perspective?
$this->Binder->Doc->User->Behaviors->attach('Containable');
$this->Binder->Doc->User->contain(array('Doc'=>'Binder'));
$user = $this->Binder->Doc->User->find('all',array('conditions'=>'User.id'=>$user_id));
The 'DocsUser' association should be detected anyway.
From a Binders perspective maybe
In your Binders MODEL add ( please check table, key and model names in case I made a typo )
function getBindersByUserSql($user_id)
{
$dbo = $this->getDataSource();
$subQuery = $dbo->buildStatement(
array(
'fields' => array('DISTINCT(Doc.binder_id)'),
'table' => "docs_users",
'joins' => array(
array('table' => 'users',
'alias' => 'User',
'type' => 'INNER',
'conditions' => array('DocsUser.user_id = User.id')
),
array('table' => 'docs',
'alias' => 'Doc',
'type' => 'INNER',
'conditions' => array('Doc.id = DocsUser.doc_id')
)
),
'alias'=>"DocsUser",
'conditions' => array("User.id"=>$user_id),
'order' => null,
'group' => "Doc.binder_id"
),
$this
);
return $dbo->expression($subQuery);
}
Then in your binders CONTROLLER try
$this->Binder->Behaviors->attach('Containable');
$this->Binder->contain(array('Doc'));
$conditions = array();
$conditions = $this->Binder->getBindersByUserSql($this->Auth->user('id'));
$binders = $this->Binder->find('all',array('conditions'=>$conditions)));
Any good?