I apologize in advance if this question has been answered elsewhere; I searched high and low for an existing answer but came up empty. Perhaps I am looking with the wrong keywords.
I have three models, two (Student and Tutor)of which have a $hasMany relationship with a common model(Appointment) which has a $belongsTo relationship with both of the other models. Exact models are below...
A Student hasMany Appointments...
class Student extends AppModel {
var $hasMany = array (
'Appointment' => array (
'foreignKey' => 'student_id'
)
);
A Tutor hasMany Appointments...
class Tutor extends AppModel {
var $hasMany = array (
'Appointment' => array (
"foreignKey" => 'tutor_id'
)
);
An Appointment belongsTo a Student and a Tutor...
class Appointment extends AppModel {
var $belongsTo = array (
'Student' => array (
'foreignKey' => 'student_id'
),
'Tutor' => array (
'foreignKey' => 'tutor_id'
)
);
When looking at a student's record (e.g. 'app/student/view/4') I need to be able to list the dates of their upcoming appointments and the name of the tutor for that appointment. So I need to access data from the Tutor model (name) while looking at the Student model. Right now I have only been able to accomplish this using $this->Student->query() to pull the related tutor records, but this results in the student's information repeating in the resulting array.
All of the $hasMany and $belongsTo relationships work just fine. Does CakePHP have a built-in function that I am missing to get the related information, or does this need to be done longhand somewhere?
You should consider using Containable. In case you're not familiar with it, it's a Behavior which you can attach to models and use it to define precisely which associated models are to be included in a find, as many models and as deep as you like.
I always use it for all models, by setting it in AppModel:
var $actsAs = array('Containable');
Then in the controller:
$this->Student->find('first', array(
'conditions' => ...,
'contain' => array('Appointment' => 'Tutor'),
...
));
Related
Why are my global belongsTo conditions defined within a model not being applied?
I'm using CakePHP v2.4.2.
In model Order.php:
public $belongsTo = array(
...,
'Agent' => array(
'className' => 'Party',
'foreignKey' => 'agent_id',
'conditions' => array(...)
),
...
In controller OrdersController.php:
$agents = $this->Order->Agent->find('list');
In rendered view the following SQL statement is being applied:
SELECT `Agent`.`id`, `Agent`.`name` FROM `zeevracht2`.`parties` AS `Agent` WHERE 1 = 1;
I tried different conditions, but even a simple string containing true isn't being applied (while adding this condition to PartiesController.php within a $this->Order->Agent->find(); works fine:
$agents = $this->Order->Agent->find('list', array(
'conditions' => array('true')
));
leads to:
SELECT `Agent`.`id`, `Agent`.`name` FROM `zeevracht2`.`parties` AS `Agent` WHERE true;
After collaborating on IRC I found the answer on my own question.
It seems that conditions in the belongsTo condition of a model is only applied to the JOIN when querying an Order.
I was trying to filter Party on specific roles, e.g. Agent, which is an alias of a Party with a role as agent. So the association should be conditioned with a role set as agent. Ideally, this would automatically condition any $this->Order->Agent->find() calls. But unfortunately, this is not possible due to a technical issue which is being addressed in the development of version 3 of CakePHP.
The solution is to have two types of conditions on the association: one for the JOIN and one for the association itself.
Why one for the JOIN? E.g. when a Post belongs to User, but only post.validated should be displayed.
If you’re trying to find the Agent that a particular Order belongs to, then you should get the record back when querying orders. So something like:
<?php
class OrdersController extends AppController {
public function view($id) {
$order = $this->Order->findById($id);
pr($order); exit;
}
}
Should yield something like:
Array
(
[Order] => Array
(
[id] => 83
…
)
[Agent] => Array
(
[id] => 1
…
)
)
In your question where you then make an additional model call like $this->Order->Agent->find('list');, that’s making a new query, which will fetch all agents with no conditions. The conditions key, where you pass true, will have no affect, because that’s not a condition. Conditions should be an array, like this:
$this->Order->Agent->find('all', array(
'conditions' => array(
'Agent.id' => 1
)
));
But as I say, if your Order model belongs to your Agent model, then you should get an Agent result set back when you get your Order result set. If not, try adding the Containable behavior to your Order model:
<?php
class Order extends AppModel {
public $actsAs = array(
'Containable'
);
}
Try this:
public $belongsTo = array(
...,
'Agent' => array(
'className' => 'Party',
'foreignKey' => false,
'conditions' => array('joinField'=>'joinedField', ...more conditions)
),
Is it possible to access more than one model deep in a relationship in Lithium?
For example, I have a User model:
class Users extends \lithium\data\Model {
public $validates = array();
public $belongsTo = array("City");
}
and I have a City model:
class Cities extends \lithium\data\Model {
public $validates = array();
public $belongsTo = array("State");
}
and a State model, and so on.
If I'm querying for a User, with something similar to Users::first(), is it possible to get all the relationships included with the results? I know I can do Users::first(array('with' => 'City')) but I'd like to have each City return its State model, too, so I can access it like this:
$user->city->state->field
Right now I can only get it to go one deep ($user->city) and I'd have to requery again, which seems inefficient.
Using a recent master you can use the following nested notation:
Users::all( array(
'with' => array(
'Cities.States'
)
));
It will do the JOINs for you.
I am guessing you are using SQL?
Lithium is mainly designed for noSQL db´s, so recursiveness / multi joins are not a design goal.
You could set up a native sql join query and cast it on a model.
query the city with Users and State as joins.
you could setup a db based join view and li3 is using it as a seperate model.
you probably should split your planned recursive call into more than one db requests.
Think about the quotient of n Cities to m States. => fetch the user with city and then the state by the state id. => pass that as two keys or embed the state info. This would be acceptable for Users::all() queries aswell.
Example using Lithiums util\Set Class:
use \lithium\util\Set;
$users = Users::all(..conditions..);
$state_ids = array_flip(array_flip(Set::extract($users->data(), '/city/state_id')));
$stateList = States::find('list',array(
'conditions' => array(
'id' => $state_ids
),
));
You can set up relationships in this way, but you have to use a more verbose relationship definition. Have a look at the data that gets passed when constructing a Relationship for details about the options you can use.
class Users extends \lithium\data\Model {
public $belongsTo = array(
"Cities" => array(
"to" => "app\models\Cities",
"key" => "city_id",
),
"States" => array(
"from" => "app\models\Cities",
"to" => "app\models\States",
"key" => array(
"state_id" => "id", // field in "from" model => field in "to" model
),
),
);
}
class Cities extends \lithium\data\Model {
public $belongsTo = array(
"States" => array(
"to" => "app\models\States",
"key" => "state_id",
),
);
}
class States extends \lithium\data\Model {
protected $_meta = array(
'key' => 'id', // notice that this matches the value
// in the key in the Users.States relationship
);
}
When using the States relationship on Users, be sure to always include the Cities relationship in the same query. For example:
Users::all( array(
'with' => array(
'Cities',
'States'
)
) );
I have never tried this using belongsTo relationships, but I have it working using hasMany relationships in the same way.
When I was working on my current project, I ran into a rather complex issue. I'll point out my problem much more detailed right now:
There are three Models: User, UsersExtendedField and UsersExtended.
UsersExtendedField contains custom fields that can be managed manually. All custom fields can be shown in the Users view as well, and be filled out of course. The values are then stored in UsersExtended, which has got two foreignKeys: user_id and field_id.
The relations look like this: User hasMany UsersExtendedField, UsersExtendedField hasMany UsersExtended, UsersExtended belongsTo User, UsersExtendedField.
The problem: When accessing the Users view, a form with user information input is shown. Any UsersExtendedFields are available as well, and since these hasMany UsersExtended, they've got plenty of UsersExtended values. But I want to reduce those to only the value(s) that belong to the User, whose view is shown at the moment. Here are my (desired) relations:
Croogo::hookBehavior('User', 'Crooboard.ExtendedUser', array(
'relationship' => array(
'hasMany' => array(
'UsersExtendedField' => array(
'className' => 'Crooboard.UsersExtendedField',
'foreignKey' => '',
'conditions' => array('status' => 1)
),
),
),
));
class UsersExtendedField extends AppModel {
var $name = 'UsersExtendedField';
var $displayField = 'fieldname';
var $hasMany = array(
'UsersExtended' => array(
'className' => 'Crooboard.UsersExtended',
'foreignKey' => 'field_id',
'conditions' => array(
'UsersExtended.user_id = User.id'
)
),
);
}
This is not the full code, these are the important parts. The problem starts right where I wrote 'UsersExtended.user_id = User.id'. Obviously, this won't work. But I do not have any idea how to access the User.id here. I also could not imagine a HABTM structure to solve this task. Do you have any idea how to get the semantics of this 'UsersExtended.user_id = User.id' to work?
Thank your very much for taking the time to read through this and helping me!
It sounds like you need to set up your HABTM relationship properly.
You already have the join table, UsersExtended, which contains your foreign keys.
Remove all previous relationships and set up HABTM in each of your User and UserExtendedField models.
The relationship code in your User model would look like this:
var $hasAndBelongsToMany = array(
'UsersExtended' => array(
'className' => 'UsersExtended',
'joinTable' => 'UsersExtended', //assuming this is the
//name of that model's table
'foreignKey' => 'user_id',
'associationForeignKey' => 'field_id'
)
);
For more information check out the page in the cakephp book
In addition, this blog post helped me grasp the relationship concepts when I was learning cakephp.
Basically, im trying to learn the basics of cakephp and im stuck at sorting the relationships out. I have tried loads of ways with no success.
I have a lead table which needs to connect to the contact table, there can be many leads to one contact. Im not sure how to do this, could someone please help?
lead model:
<?php
class Lead extends AppModel {
var $name = 'Lead';
var $belongsTo = array(
'Contact' => array(
'className' => 'Contact',
'foreignKey' => 'contact_id'
)
);
}
?>
contact
<?php
class Contact extends AppModel {
var $name = 'Contact';
var $hasMany = array(
'Lead' => array(
'className' => 'Lead',
'foreignKey' => 'contact_id'
)
);
}
?>
Since once contact can have many leads, you'll want to add
var $hasMany = 'Lead';
to your User class.
And since I assume that Lead has a foreign key referencing the 'owning' User, you'll want to add
var $belongsTo = 'User';
to your Lead class.
This will let you access leads and users from both sides of the relationship (from the lead perspective, and from the user perspective).
See the docs for hasMany and belongsTo for more info.
say you have 3 models : user, hair_color, and eye_color
user hasOne hair_color
user also hasOne eye_color
however
var $hasOne = 'hair_color';
var $hasOne = 'eye_color';
obviously wont work. So how do you implement many hasOne relationships in a single model?
I assume the answer is in the cookbook, Im going over that area now, I suspect it has something to do with passing an array to $hasOne, but no example of doing this.
var $hasOne = array('HairColor', 'EyeColor');
// hasOne is an array of model names **not file names**
OR
var $hasOne = array(
'HairColor' => array(
'className' => 'HairColor',
...
),
'EyeColor' => array(
'className' => 'EyeColor',
...
)
);
You should read manual http://book.cakephp.org/view/80/hasOne