cakephp - using loadmodel and getting data related to the loaded model - php

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

Related

Deep select accociated model data Cakephp 2.x

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));

Join single item from related table based on min of field

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

belongsTo relationship & filtering in cakePHP

I'm a newbie to cakePHP and I've taken on creating a very small "status reporting" project, that would allow a user to report their current status on a project that they were assigned on.
I'm currently using the acl, auth, and session components to allow for multiple tier users to manage one another with an admin creating users, projects, and assigning them to one another. I have also fixed it so that when a user is logged in and goes to add a "status" that their login session automatically takes care of the "user_id" for the status:
$this->data['Status']['user_id'] = $this->Auth->user('id');
What I need help doing now is filter the options for the project that the user can pick to add a "status" to.
I currently have a setup with a table that holds the relationships between users and projects named *projects_users* with *id, project_id, user_id* as fields. But, after baking this setup, when the user is adding a "status" the drop down menu provides all projects rather than strictly the ones they are "assigned to."
I would like to filter the user's options to the projects that they are assigned to. Is there considerable more relationships I should be setting up or is this a pretty easy little function to write? Any help would be greatly appreciated and if I have left out information, I'd be glad to post anything else.
Here is the Status Model setup:
class Status extends AppModel {
var $name = 'Status';
//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' => ''
),
'Project' => array(
'className' => 'Project',
'foreignKey' => 'project_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
My User Model looks like this:
var $belongsTo = array(
'Group' => array(
'className' => 'Group',
'foreignKey' => 'group_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
var $hasMany = array(
'Status' => array(
'className' => 'Status',
'foreignKey' => 'user_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
);
var $hasAndBelongsToMany = array(
'Project' => array(
'className' => 'Project',
'joinTable' => 'projects_users',
'foreignKey' => 'user_id',
'associationForeignKey' => 'project_id',
'unique' => true,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
)
);
In the database, You should have a column called project_id in the user table that is associated with the project table's id column.
Then, in your User model, you should create a hasMany relationship with the Project model.
class User extends AppModel {
var $name = 'User';
var $hasMany = 'Project';
}
Now, when you fetch a particular user, you will get all projects that the user is associated with.

CakePHP: Retrieving records based on field in related model

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)

How to change the sequence of 'joins' in CakePHP?

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);
}

Categories