Attempt to replace CakePHP model binds with Containable is not working - php

I am trying to get a list of all Visitors with only the latest visit information and the associated VisitType description for an API call and I need to be able to do this in one find. Previously we had the second code block below, but this returns multiple records for Visitors when the Visitor has more than one associated Visit record (one for each Visit Record). I just need to get the most recent (MAX(Visit.visit_date) but obviously cannot use this if we intend to join against VisitType) with it's associated VisitType.type_description.
The beforeFind() function is on the Visitor model since we are trying to get a list of Visitors.
I am getting the follow issue, when I tried to use the Containable code below:
Warning (512): Model "Site" is not associated with model "Site" [CORE/Cake/Model/Behavior/ContainableBehavior.php, line 343]
Warning (512): Model "SubjectStatus" is not associated with model "SubjectStatus" [CORE/Cake/Model/Behavior/ContainableBehavior.php, line 343]
I have these associations established:
Visitor hasMany Visits, a Visit hasOne Visitor
Visitor hasOne Site, a Site hasMany Visitors
VisitorStatus hasMany Visitors, Visitor belongsTo VisitorStatus
Visit hasOne VisitKind, VisitKind hasMany Visits
Failing Containable code:
public function beforeFind($options, $primary=false) {
if ($this->__AjaxDataLoad) {
// Replacing a bunch of Model rebinds with Containable
$this->Behaviors->load('Containable');
$this->contain(array(
'Site' => array(
'conditions' => array('Site.id = Visit.site_id'),
'field' => array('Site.site_name')
),
‘VisitorStatus' => array(
'conditions' => array(‘VisitorStatus.id = Visitor.visit_status_id'),
'field' => array(‘VisitorStatus.status')
),
'Visit' => array(
'conditions' => array('Visit.subject_id = Visitor.id'),
'order' => 'Visit.visit_date DESC',
'limit' => 1,
'VisitType' => array(
'conditions' => array('Visit.visit_type_id = VisitType.id'),
'fields' => array('VisitType.type_description'),
)
)
));
}
}
Previously, the follow code "works" with the undesirable side-effect of including multiple rows per visitor, one row per Visit:
public function beforeFind($options, $primary=false) {
if ($this->__AjaxDataLoad) {
$this->unbindModelAll();
$this->bindModel(array(
'belongsTo' => array(
'Site' => array(
'className' => 'Site',
'foreignKey' => 'site_id',
),
‘VisitorStatus' => array(
'className' => ‘VisitorStatus',
'foreignKey' => ‘visitor_status_id',
)
),
'hasOne' => array(
'Visit' => array(
'className' => 'Visit',
'foreignKey' => ‘visitor_id',
'order' => 'Visit.visit_date DESC’,
‘limit’ => 1 // <—————————————— THIS DOES NOTHING
),
'VisitType => array(
'foreignKey' => false,
'conditions' => array("Visit.visit_type_id = VisitType.id"),
'fields' => array('VisitType.type_description’)
)
)
));
}
}
Is there something wrong with the Containable structure I am attempting?
I have configure Debug = 2, CakePHP version 2.2.5

The solution I ultimately came up with complete circumvents CakePHP relationship techniques. It came down to using virtualFields and a subquery as the last resort.
public function beforeFind($options) {
if ($this->__AjaxDataLoad) {
$options['joins'] = array(
array(
'table' => 'visits',
'alias' => 'Visit',
'type' => 'LEFT',
'conditions' => array('Visit.visitor_id = Visitor.id')
)
);
$options['group'] = array('Visitor.id');
if (is_null($options['fields'])) {
$options['fields'] = array(
'Visitor.*', 'Site.*', 'VisitorStatus.*'
);
}
$this->virtualFields['last_visit_date'] = 'MAX(Visit.visit_date)';
$this->virtualFields['last_visit_kind'] = 'SELECT visit_kinds.kind FROM visits INNER JOIN visit_kinds ON visits.visit_kind_id = visit_kinds.id WHERE visits.visitor_id=Visitor.id ORDER BY visits.visit_date DESC LIMIT 1';
}
return $options;
}

Related

Delete record from multiple tables when using a View CakePHP 2.8.x

I am using CakePHP 2.8.x and i'm looking for a way to delete records from 2 tables with 1 delete action.
I created a View from 3 tables: visitors, guests and registrations. These tables are connected through visitor_id.
In the Visitor Model i added the View in $useTable. Now when i press delete on, for example, visitor 1, i want to delete the records from visitor 1's guests and registrations tables and keep the visitor information in the visitor's table
I did found $this->Visitor->delete($id), but what parameters do i need to add to this function, so i can delete just the records from the guests and registrations tables?
If i need to give more information, i'm happy to help! Thx in advance
Update: The tables are associated. These are the models:
Visitor:
public $hasMany = array(
'Guest' => array(
'className' => 'Guest',
'foreignKey' => 'visitor_id',
'dependent' => false,
'exclusive' => false,
),
'Registration' => array(
'className' => Registration',
'foreignKey' => 'visitor_id',
'dependent' => false,
'exclusive' => false,
),
);
public $belongsTo = array(
);
Registration:
public $belongsTo = array(
'Visitor' => array(
'className' => 'Visitor',
'foreignKey' => 'visitor_id',
),
);
Guest:
public $belongsTo = array(
'Visitor' => array(
'className' => 'Visitor',
'foreignKey' => 'visitor_id',
),
there's the deleteAll() function in cake 2 (see the cookbook)
you have to pass to the function an array of conditions. In your case you want to delete all the Guests that have visitor_id = $id (the same for Registration).
$this->Visitor->Guest->deleteAll(array('visitor_id' => $id));
$this->Visitor->Registration->deleteAll(array('visitor_id' => $id));
this will delete all the Guests and the Registrations of the Visitor without touching the Visitor itself
you can try it,its worked for me
public function your_action($id=null){
$this->MainMdel->ConnectedModel1->deleteAll(array('ConnectedModel_id' => $id));
$this->MainMdel->ConnectedModel2->deleteAll(array('ConnectedModel_id' => $id));
}

Cakephp Model ignores condition in belongsTo

I have 2 Models that are joined but not thrue the id's but with another field.
class Post extends AppModel{
public $useDbConfig = 'test';
public $actsAs = array('Containable');
public $belongsTo= array(
'User' => array(
'className' => 'User',
'foreignKey' => false,
'type' => 'inner',
'conditions' => array('User.field = Post.field')
));
}
But the cakephp is not connecting the 2 models correctly. To every Post it just gives the the first user (User with id 1). When I check the SQL-Statements the User statement is following:
SELECT `User`.`id`, `User`.`group_id`... FROM `users` AS `User` WHERE 1 = 1
Any ideas why this is not working?
I also tried joining the models manually:
$this->paginate = array(
'limit' => '15',
'joins' => array(
'INNER JOIN users User ON (User.field = Post.field)'
)
);
$posts= $this->paginate('Post');
Any ideas?
EDIT
I totally forgot to mention that these 2 tables are in different databases. Maybe there is the problem. But I had no problem joining tables over different databases so far.
EDIT
I was wrong about the join so I changed it from "belongsTo" to "hasOne" since a post can only be written by one user. But still no changes.
You can use the foreignKey parameter to any field, not necessarily the id:
Post model:
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'some_user_field'
)
);
User model:
public $hasMany = array (
'Post' => array (
'className' => 'Post',
'foreignKey' => 'some_user_field',
'dependent' => true
)
);
According to the book:
It is not mandatory to follow CakePHP conventions. You can easily
override the use of any foreignKey in your associations definitions.
Nevertheless, sticking to conventions will make your code less
repetitive and easier to read and maintain.
Otherwise, make joins manually:
$this->paginate = array(
'limit' => '15',
'joins' => array(
array(
'table' => 'users',
'alias' => 'User',
'type' => 'inner',
'conditions' => array('User.field = Post.field')
)
)
);
I found a solution although I don't understand why the others won't work. I put
$this->Post->primaryKey = 'field';
right before
$posts= $this->paginate('Post');
and now it works. Can anyone tell me why the other ways won't work?

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

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

CakePHP retrieve data, associate models, containable behavior

I can't seem to figure out how the containable behavior works, or how to retrieve the data from (and based) on associated models.
I need to put the following MySQL query in the cakephp find('all') function, I know i can use the query builder for the sql syntax but I want to use find function and want to figure out how it works.
SELECT events.id, events.name FROM events, user_roles
WHERE user_roles.event_id = events.id
AND user_roles.user_id = 1
The idea is simple, in the user_roles the user_id, role_id and event_id are stored. Now I want to retrieve all events for a certain user (here user_id = 1). I tried some variations on the following:
$data = $this->Event->find('all',array(
'contain' => array(
'UserRole' => array(
'fields' => array('id'),
'conditions' => array(
'UserRole.user_id' => $this->Auth->user('id')
)
)
)
));
But this keeps returning all events. What I want is an array like (or any other result with the same associated data):
array(
[0] => array(
[Event] => array(
'id' => 1,
'name' => 'Event Name'
),
[UserRole] => array(
'role_id' => 2,
'event_id'=> 1
)
[1] => array(
.... etc ...
)
The following models and associations are defined
class Event extends AppModel {
public $hasMany = array(
'UserRole' => array(
'className' => 'UserRole',
'foreignKey' => 'event_id',
'dependent' => false,
)
)
)
class UserRole extends AppModel {
public $belongsTo = array(
'Event' => array(
'className' => 'Event',
'foreignKey' => 'event_id'
),
)
)
So how does it work ;).
Edit:
I forgot to mention that I've set in AppModel.php:
public $recursive = -1;
Containable finds the associated records for the original query. What you are doing is a find('all') on your Event model with absolutely no conditions, so it will return all Events.
Then Containable will retrieve all the UserRole records that are for the given user_id and are also associated to the Event by the event_id
If you want to only retrieve events for the given user, you could do it one of two ways
Via the Event->UserRole association and chained models:
$data = $this->Event->UserRole->find('first', array(
'conditions' => array('UserRole.user_id' => $this->Auth->user('id'),
'contain' => array('Event')
));
$events = $data['Event'];
or
Via your current code and then using the Hash::extract method to get the Events that match. (Hash is a CakePHP core class)
$events = Hash::extract('/UserRole[user_id='.$this->Auth->user('id').']/..', $data);

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)

Categories