I'm trying to retrieve some data through two model relationships with CakePHP. The models and their associations are as follows:
User hasOne Profile HABTM Skill
I would like the user's skills to be returned when I do a find() operation on the User model, and right now it isn't returned. Here's my find call which is being executed against the User model:
$this->paginate = array(
'conditions' => array(
'OR' => array(
'Profile.firstname LIKE' => "%$q%",
'Profile.lastname LIKE' => "%$q%"
)
)
);
It's returning user data and profile data, but not any skill data. I tried setting recursive to 2 or 3, but that doesn't help either. The only way I can get skill data is if I run find() against the Profile model, but I can't do that. For clarification here's the relevant models and their relationships:
// app/Model/User.php
class User extends AppModel {
public $hasOne = 'Profile';
}
// app/Model/Profile.php
class Profile extends AppModel {
public $belongsTo = 'User';
public $hasAndBelongsToMany = 'Skill';
// app/Model/Skill.php
class Skill extends AppModel {
public $hasAndBelongsToMany = 'Profile';
Can anyone help me get the users skills when retrieving user data? Thanks.
Use CakePHP's Containable Behavior. Your find will then look something like this:
$this->User->find('all',array(
'conditions' => array(
'OR' => array(
'Profile.firstname LIKE' => "%$q%",
'Profile.lastname LIKE' => "%$q%"
)
),
'contain' => array(
'Profile' => array(
'Skill'
)
)
));
MUCH simpler, easier to read, and voila - you get the data you want without needing to use the dreaded 'recursive'.
Related
Right now I have this:
Groups
Inside Groups we have Subgroups
Inside Subgroups we have Comments
Subgroups belongs to Groups 1, 2, 3 ; subgroups table have group_id field
Comments belongs to Subgroups A, B, C ; comments table have subgroup_id field
My models:
CommentsGroup.php
<?php
App::uses('AppModel', 'Model');
class CommentsGroup extends AppModel {
public $useTable = 'comment_groups';
}
CommentsSubGroup.php
<?php
App::uses('AppModel', 'Model');
class CommentsSubGroup extends AppModel {
public $useTable = 'comment_subgroups';
public $belongsTo = array(
'CommentsGroup' => array(
'className' => 'CommentsGroup',
'foreignKey' => false,
'conditions' => ['`CommentsGroup`.`id` = `CommentsSubGroup`.`group_id`']
)
);
}
Comment.php
<?php
App::uses('AppModel', 'Model');
class Comment extends AppModel {
public $belongsTo = array(
'CommentsSubGroup' => array(
'className' => 'CommentsSubGroup',
'foreignKey' => false,
'conditions' => ['`CommentsSubGroup`.`id` = `Comment`.`subgroup_id`']
)
);
}
When I try from my controller to get the subgroup_id related to the comment it's ok. When I try to get more (the group_id linked to the subgroup_id), I fail.
Query without recursive is ok, otherwise I have:
$data = $this->_Model->find('all', ['conditions' => ['subgroup_id' => $id], 'recursive' => 2, 'order' => [$this->_Modelname . '.id' => 'DESC']]);
Take in consideration $this->_Model equals to Comment .
The error I have:
Database Error Error: SQLSTATE[42S22]: Column not found: 1054 Unknown
column 'CommentsSubGroup.group_id' in 'where clause'
SQL Query: SELECT CommentsGroup.id, CommentsGroup.name FROM
botobot_comments.comment_groups AS CommentsGroup WHERE
CommentsGroup.id = CommentsSubGroup.group_id
Any guess ? Or I am wrong and I should use $hasMany relationship ?
Thanks.
You are correct in using a belongsTo relationship but you need to specify the foreignKey attribute in order for the relationships to work. You also need to take the join condition out of the conditions key (as Cake can figure that out based on the foreign key).
For example:
App::uses('AppModel', 'Model');
class Comment extends AppModel {
public $belongsTo = array(
'CommentsSubGroup' => array(
'className' => 'CommentsSubGroup',
'foreignKey' => 'subgroup_id'
)
);
}
You also need to follow Cakes naming convention with your models and tables otherwise youll need to explicitly declare your table names and primary keys in their respective models.
In your case, CommentsSubGroup should be CommentSubGroup which would assume the table called comment_sub_group and a primary key of comment_sub_group_id
User Model has relation:
public $hasMany = array(
'MyRecipe' => array(
'className' => 'Recipe',
)
);
I want to select all users who have recipes with ID: 1,2
How I can use that conditions in select:
$this->User->find('all', array(
'conditions' => array(
'Recipe.Id' => [1,2]
)
));
But in this example I will get also Users without recipes, how to prevent that ?
please give this relation in User model
class User extends AppModel
{
var $name = 'User';
var $belongsTo = array("Recipe");
}
and in user controller your query as
$list = $this->User->find('all',array("conditions"=>array("recipe_id IN"=> [1,2] )));
its gives output which you want..
I am sorry if the title is a bit confusing, I just don't know how this is properly called. I have a table structure like this for my CakePHP project.
users id, name, surname, userrecords
userrecords id, user_id, records_id
records id, description
I understand that to access the userrecords middle table in my users view I have to do something like
$user['userrecords']['id'];
How can I neatly access description in records table through a users view?
You didn't specify whether you're using CakePHP 2.x or 3.x, so I provided solutions for both.
The relationship you are referring to is called a "Has And Belongs To Many" relationship. Since both of your model are associated to a linking table (userrecords), you can freely associate as many records to as many users as you want.
First, I would consider renaming your 'userrecords' table to 'users_records' to play nicely with CakePHP.
First, define your relationship within your Users model:
// Using CakePHP 2.x:
class User extends AppModel {
public $actsAs = array('Containable'); // Instantiate Containable behavior
public $hasAndBelongsToMany = array(
'Record' =>
array(
'className' => 'Record',
'joinTable' => 'users_records',
'foreignKey' => 'user_id',
'associationForeignKey' => 'record_id'
)
);
}
// Using CakePHP 3.x:
class UsersTable extends Table
{
public function initialize (array $config)
{
$this->belongsToMany('Records', [
'joinTable' => 'users_records' // Defines our linking table
]);
}
}
Now, we must define our relationship within our Records model:
// Using CakePHP 2.x:
class Record extends AppModel {
public $actsAs = array('Containable'); // Instantiate Containable behavior
public $hasAndBelongsToMany = array(
'User' =>
array(
'className' => 'User',
'joinTable' => 'users_records',
'foreignKey' => 'record_id',
'associationForeignKey' => 'user_id'
)
);
}
// Using CakePHP 3.x:
class RecordsTable extends Table
{
public function initialize (array $config)
{
$this->belongsToMany('Users', [
'joinTable' => 'users_records' // Defines our linking table
]);
}
}
Now, we can access the associated records freely from each model using the ORM's contain method:
// Using CakePHP 2.x:
// Getting 'User' and associated 'Record' models in Controller:
$this->loadModel('User');
$this->User->find('all', array('contain' => 'Record'));
// Getting 'Record' and associated 'User' models in Controller:
$this->loadModel('Record');
$this->Record->find('all', array('contain' => 'User'));
// Using CakePHP 3.x:
// Getting 'User' and associated 'Record' models:
$users_table = TableRegistry::get('Users');
$users = $users_table->find()->contain('Records')->all();
// Getting 'Record' and associated 'User' models:
$records_table = TableRegistry::get('Records');
$records = $records_table->find()->contain('Users')->all();
Read the Cookbook, it will make your life a million times easier:
CakePHP 2.x Containable Behavior
CakePHP 2.x Has And Belongs To Many Relationship
CakePHP 3.x belongsToMany Relationship
CakePHP 3.x Retrieving Associated Data
CakePHP 3.x Eager Loading Associations
Read this,this may be helpful for you
http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html
Because your table structure as below :
User -> UserRecord -> Record
So that you can only get [Record] via [UserRecord]
You should set recursive property in find command.
Please refer for more information about recursive at this link : what is the meaning of recursive in cakephp?
I hope this answer doesn't misunderstand your question.
I have a problem with querying associated data from a Model in CakePHP. I wrote an example to show the behavior:
TestController.php:
class TestController extends AppController
{
public $uses = array(
'User',
'Upload',
'Detail'
);
public function test(){
$result = $this->Upload->find('all', array(
'recursive' => 2,
'conditions' => array('Detail.id' => 1)
));
print_r($result);
}
}
Upload.php:
class Upload extends AppModel {
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
)
);
}
Detail.php:
class Detail extends AppModel {
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
)
);
}
User.php:
class User extends AppModel {
public $hasOne = 'Detail';
public $hasMany = array(
'Upload' => array(
'className' => 'Upload',
'foreignKey' => 'user_id',
)
);
}
When I remove the condition I get back an array with Details included. But with the condition I get the following error:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Detail.id' in 'where clause'
Looking at the SQL Queries it seems like he is not joining the tables correctly when I add the condition. Without the condition he is joining all three tables.
Is this a bug in CakePHP or am I doing anything wrong?
No, it is not a bug with CakePHP. It's simply the way it's designed, using conditions during a find on associated models will often create an invalid query. You should be using containable behavior or manually joining to use conditions on associated models.
Also, I suspect that you will not get the results you are looking for doing this way anyways. CakePHP by default uses left joins. Therefore, your results will not be limited by those associated with the desired Detail ID, but rather, it will get all uploads, all users associated with those uploads, and then only those details associated with those users that have the correct ID. The simplest way then to get what you're probably looking for is to do the query from the opposite direction:
$result = $this->Detail->find('all', array(
'recursive' => 2,
'conditions' => array('Detail.id' => 1)
));
EDIT: If you do want to do left joins, then make your query this way:
$result = $this->Upload->find('all', array(
'contain' => array('User' => array('Detail' => array('conditions' => array('Detail.id' => 1))),
));
I have a model class in CakePHP defined like this:
class Programme extends AppModel {
public $hasOne = array(
'ProgrammeLikes' => array(
'className' => 'ProgrammeLikes',
'fields' => array('likes'));
}
When retrieving my models from the database they are returned as an array with an array keyed to 'Programme' and a separate array keyed to 'ProgrammeLikes' (which contains the 'likes' value correctly). In order to reduce the changes necessary to existing code I want the 'likes' value to be within the 'Programme' array.
Is this possible?
Thanks in advance
Use virtualFields here to get this thing to be done.
class Programme extends AppModel {
public $hasOne = array(
'ProgrammeLikes' => array(
'className' => 'ProgrammeLikes',
'fields' => array('likes')
);
public $virtualFields = array(
'likes' => 'SELECT likes FROM programme_likes AS ProgrammeLikes WHERE ProgrammeLikes.id = Programme.programme_likes_id'
);
// Where programme_likes_id is the foriegnkey for Programme model
}
Note: I assumed programme_likes is your table name for ProgrammeLikes Model and programme_likes_id is the foriegnkey for
Programme Model, so you can arrange the query in your own way that suits your requirement.