i have this model structure Client->Project->Landlord->Property
Property belongsto Landlord
Landlord belongsto Project
Project belongsto Client
inside the properties controller i want all the properties that belong to a specific client
How do i do this i have tried it with containable but it always gives me all Properties and not the ones that are related with their foreign keys.
$properties = $this->Property->find('all', array(
'contain' => array(
'Landlord' => array(
'Project' => array(
'conditions' => array(
'client_id' => $user['client_id'] // Passed parameter
)
),
'conditions' => array(
'Landlord.project_id' => 'Project.Id'
)
)
)
));
This isn't doing anything, it evens seems to ignore any typo's i made. what am i doing wrong here?
Oke i figured it out with joins this is what i came up with.
$joins = array(
array('table'=>'projects',
'alias' => 'Project',
'type'=>'inner',
'conditions'=> array(
'Project.client_id = '.$user['client_id']
)),
array('table'=>'landlords',
'alias' => 'Landlord',
'type'=>'inner',
'conditions'=> array(
'Project.id = Landlord.project_id','Landlord.id = Property.landlord_id'
)),
);
$properties = $this->Property->find('all',array('joins'=>$joins,'recursive'=>-1));
Related
UPDATED!
i am relativly new in cakephp but having experience with mysql and php.
The model look like:
Person->Father
Father is self refered to person.
I wrote the following based on mysql query which gives back the father of "1" person
mysql:
SELECT `Father`.name,`Father`.id from persons as `Father` left join persons as `Person` on `Person`.`father_id`=`Father`.`id` where `Person`.id=1
cakephp
$options = array(
'fields' => array(
//'Father.name',
'Father.id',
),
'joins' => array(
array(
'conditions' => array(
'Person.father_id = Father.id',
),
'table' => 'persons',
// 'alias' => 'Person', i commented because having conflict with scaffolded model
'type' => 'left',
),
),
'conditions' => array(
'Person.id' => '1',
),
'contain' => array(
'Person','Father',
),
);
$data = $this->Person->find('first', $options);
$fatherquery=$this->Person->find('first',array('conditions'=>array('Person.id'=>$data['Father']['id'])));
To get the same as mysql i have add this extra line(the last one $father=....,but now it seems subquery and look like the join isn't working) because of Father and Person are not the same model and if i have
$data['Father']['name'] and $data['Person']['name'] they are not equal
By the way i have a solution already,but maybe i misunderstand some concept.
Is there a way the get the mysql query easier?
Try this. (Notice the lack of a contain. The only reason to use contain is if you're not already getting the data in the main-model's find() or a join):
//Person model (cake 2.x code)
$this->find('first', array(
'fields' => array(
'Father.id',
'Father.name'
),
'conditions' => array(
'Person.id' => 1
),
'joins' => array(
array(
'table' => 'persons',
'alias' => 'Father',
'type' => 'left',
'conditions' => array(
'Person.father_id = Father.id'
)
)
)
));
I have an Item model which has the following associations:
public $hasOne = array(
'Project' => array(
'className' => 'Project',
'foreignKey' => 'item_id'
)
);
public $hasMany = array(
'ItemPic' => array(
'className' => 'ItemPic',
'foreignKey' => 'item_id',
'dependent' => false
)
);
I am wanting custom data for different views of Item. It seems like CakePHP automatically includes Project data (maybe because it is hasOne?) and does not include the ItemPic data. In the index I really don't even want the Project data... however, I do want the ItemPic data. For each Item record pulled, I want a single ItemPic record joined to it. This ItemPic should be basically ItemPic.item_id = Item.id and ORDER BY ItemPic.rank LIMIT 1.
The purpose of this is basically so that in the index I can show a list of Items and a picture associated with each item. I would like all of the images along with the Project data in the view for a single Item, but not in the list/index.
I was told I could use containable like this:
// In the model
public $actsAs = array('Containable');
// In the controller
$this->paginate = array(
'conditions' => $conditions,
'contain' => array(
'ItemPic' => array(
'fields' => array('file_name'),
'order' => 'rank',
'limit' => 1
)
)
);
The above actually works how I want... however, I was also told that doing this would cause an extra query to be ran for every single Item... which I feel I should avoid.
I tried doing this, but I get duplicate data and it doesn't attach any ItemPic data:
$this->paginate = array(
'conditions' => $conditions,
'joins' => array(
array(
'table' => 'item_pics',
'alias' => 'ItemPic',
'type' => 'LEFT',
'conditions' => array(
'ItemPic.item_id = Item.id'
),
'order' => 'rank ASC',
'limit' => 1
)
)
);
$paginated = $this->Paginator->paginate();
Can you please try this:
$this->paginate = array(
'conditions' => $conditions,
'joins' => array(
array(
'table' => 'item_pics',
'alias' => 'ItemPic',
'type' => 'LEFT',
'conditions' => array(
'ItemPic.item_id = Item.id'
),
'order' => 'rank ASC',
'limit' => 1
)
),
'fields' => array('Item.*','Project.*','ItemPic.*')
);
In the fileds section you may or may not assign "Item" , "Project" according to your requirment.
Thanks
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
As I understood from the cakephp-documentation, one of the advantages of the 'containable'-Behavior is being able to fetch fewer data if you need fewer data...
But that doesn't seem to work in my case of a connection between users and usergroups.
My associations look like:
Group
hasMany: Membership
User
hasMany: Membership
Membership
belongsTo: User, Group
(I'm not using HABTM, instead use the Model 'Membership' in between to join users and groups).
All Models implement the 'Containable'-Behavior.
Now I want to get all the members of a group with a certain id, only their ids and mail-addresses. My query is built like that:
$members = $this->Membership->find('all', array(
'conditions' => array(
'group_id' => $id
),
'contain' => array(
'User' => array(
'fields' => array('id', 'fullName')
),
)
));
But the resulting array looks like:
array(
(int) 0 => array(
'Membership' => array(
'id' => '1',
'group_id' => '1',
'user_id' => '1',
'role' => 'member'
),
'Group' => array(
'id' => '1',
'name' => 'Foo Group'
),
'User' => array(
'password' => '*****',
'id' => '1',
'name' => 'Dr. Foo'
'mail' => 'foo#bar.baz',
'role' => 'admin'
)
)
)
So there are definietely more fields fetched than I wanted to... (it's the same thing btw wenn I set the 'contain'-key to:
'contain' => array(
'User.fullName', 'User.id'
)
Am I using the containable-behavior wrong?
Your models don't seem to be acting containabl-y at all. Have you set your models to act as containable?
class Post extends AppModel {
public $actsAs = array('Containable');
}
If so, maybe the problem is with the recursion (to avoid getting the Group array with the query). Containable behavior should handle the recursion level on its own, but try setting it on the AppModel just to be sure
class AppModel extends Model {
public $actsAs = array('Containable');
public $recursive = -1;
Your first attempt
'contain' => array(
'User' => array(
'fields' => array('id', 'fullName')
),
)
looks good in terms of syntax, so it probably the actAs thing.
Also, for debugging also, try
$this->Membership->contain('User');
$this->Membership->find('all', array(
'conditions' => array(
'group_id' => $id
));
and see if you get the expected results that way.
I have the problem with the sequence of joins. The similar problem was in another question Manipulating Order of JOINS in CakePHP. The answer was to use Containable behavior. In my case that is unacceptable because I have deeper associations and containable generates too many queries. Containable does not generate joins for the three level associations. It generates additional queries for every entry from the second level table.
My query is:
$this->LevelOne->find('all', array(
'joins' => array(array(
'table' => 'level_three',
'alias' => 'LevelThree',
'type' => 'LEFT',
'conditions' => array(
'LevelThree.id = LevelTwo.level_three_field_id'
)
))
));
The problem here is that cake generates several joins but the join of the LevelThree table is done before the joins of the LevelTwo tables and that throws an SQL error "Unknown column 'LevelTwo.level_three_field_id' in 'on clause'". If the LevelThree join would be at the end of the query after all LevelTwo joins the query would be okay.
So, the question is how to change the sequence of joins?
Finally I figured out how to do that:
$this->LevelOne->unbindModel(array('belongsTo' => array('LevelTwo')));
$this->LevelOne->find('all', array(
'joins' => array(
array(
'table' => 'level_two',
'alias' => 'LevelTwo',
'type' => 'LEFT',
'conditions' => array(
'LevelTwo.id = LevelOne.level_two_field_id'
)
),
array(
'table' => 'level_three',
'alias' => 'LevelThree',
'type' => 'LEFT',
'conditions' => array(
'LevelThree.id = LevelTwo.level_three_field_id'
)
)
)
));
To those who has similar problems but in relations a-la $belongsTo, to have correct order you should set it correctly.
For example when you have such code:
var $belongsTo = array(
'Vacancy',
'Applicant' => array(
'className' => 'Person',
),
'Recruiter' => array(
'className' => 'Person',
),
'Company' => array(
'conditions' => array('Company.id = Vacancy.company_id'),
),
);
But in the result always receive results where Vacancy always joins last, you should do juts simple thing: add those "Vacancy" model not as array value, but as a key=>value, as others:
var $belongsTo = array(
'Vacancy' => array(), // Just add empty array here -- all magic is here :)
'Applicant' => array(
'className' => 'Person',
),
'Recruiter' => array(
'className' => 'Person',
),
'Company' => array(
'conditions' => array('Company.id = Vacancy.company_id'),
),
);
Now all will be in straight order: Vacancy, Applicant, Recruiter & only then Company.
Have you thought about creating a HABTM model and inserting your own 'finderQuery' to override the model query?
http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasandbelongstomany-habtm
Sometimes with complex queries, it make more sense to create the custom query so cake doesn't have to deal with it. To do that, you can just create the function in the model and then call it like you would any other query.
function customJoin(){
return $this->query('CUSTOM QUERY HERE');
}
Then call it from the controller:
$this->ModelName->customJoin();
I think sometimes we rely too heavily on the automation that a framework provides and forget that WE are in control and can implement code outside of the core. I do it all the time. Hope this helps.
public function selectdata(){
$option= $this->Group->find('all',
array(
'joins' =>
array(
array(
'table'=> 'user_groups',
'alias'=>'g',
'type'=> 'INNER',
'conditions'=> array('g.group_id =Group.id ')
),
array(
'table'=> 'users',
'alias'=>'u',
'type'=> 'INNER',
'conditions'=> array('u.id = g.user_id')
),
),'fields'=>array('Group.name,Group.created,Group.modified,Group.status,Group.created_by,u.first_name,u.last_name'),
'conditions'=>array('g.group_id=Group.created_by or g.user_id=u.id')
// ."'".$created_by."'"
)
);
var_dump($option);//die();
if(isset($this->params['requested']))
{
return $option;
}
$this->set('groups', $option);
}