How to get associated data of a model using custom join cakephp2 - cakephp

I am using cakephp2 and trying to join two tables like this :
$employee=$this -> Employee ->find ('all',array(
'joins'=>array(
array('table' => 'employee_histories', 'alias' => 'eh', 'type' => 'LEFT', 'foreignKey' => false,
'conditions' => array('eh.emp_id= Employee.emp_id'))
),
'fields'=>'Employee.*,eh.*',
));
In EmployeeHistory model there is an association with table employee_designations like this :
public $belongsTo = array(
'EmployeeDesignation' => array(
'className' => 'EmployeeDesignation',
'foreignKey' => '',
'conditions' => 'EmployeeDesignation.designation_id=EmployeeHistory.designation_id',
'fields' => '',
'order' => ''
),
);
Now is it possible to get the EmployeeDesignation result in $employee array.

You can join the employee_designations table by adding another array to your joins array. Something like this:
$employee= $this->Employee->find('all', array(
'joins'=>array(
array(
'table' => 'employee_histories',
'alias' => 'eh',
'type' => 'LEFT',
'foreignKey' => false,
'conditions' => array(
'eh.emp_id= Employee.emp_id'
)
),
// array joining the employee designations table
array(
'table' => 'employee_designations',
'alias' => 'ed',
'type' => 'LEFT',
'conditions' => array(
'eh.designation_id = ed.designation_id'
)
)
),
'fields'=>'Employee.*, eh.*, ed.*', // include ed fields
));

Related

CakePHP join only the latest entry

I have 2 tables :
ORDERS
------
id
type
LOGS
----
id
order_id
time
I want to query with cakephp so I have :
array(
'Order' => array(
'id' => '38',
'type' => 'online',
),
'Log' => array(
'time' => '2014-09-24 21:17:14'
)
)
The problem is that I want only the last order, not all orders with all logs.
I did something like this :
$ordersList = $this->Order->find('all', array(
'fields' => array(
'Order.*',
'Log.time'
),
'joins' => array(
array(
'table' => 'logs',
'alias' => 'Log',
'type' => 'right',
'conditions' => array(
'Log.order_id = Order.id'
),
)
),
);
To have only the last one, you can order the result by log.time and then take only the first record (with the param 'first' or just by fetching the first record of the recordset).
For example :
$order = $this->Order->find('first', array(
'order' => array('Log.time' => 'desc')
));
in your case :
$ordersList = $this->Order->find('first', array(
'fields' => array(
'Order.*',
'Log.time'
),
'joins' => array(
array(
'table' => 'logs',
'alias' => 'Log',
'type' => 'right',
'conditions' => array(
'Log.order_id = Order.id'
),
'order' => array('Log.time' => 'desc')
)
),
);
As information, if you want to have a simple method to join the models, take a look at the containable behavior. With this behavior, the link are setted in the model then you only have to declare which associated model you want to retrieve with your current model.
=> http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html

Search Plugin CakeDC - Dynamic Model Bind Unbind Associations

I have code working search related models using the Search Plugin, but it forces me to break the structure of my model associations. As a result, my save and edit functions do not work properly.
Here is the structure of my data.
Model:
public $belongsTo = array(
'Movie' => array(
'className' => 'Movie',
'foreignKey' => 'movie_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'MovieStarRecurrance' => array(
'className' => 'MovieStarRecurrance',
'foreignKey' => 'movie_star_recurrance_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'MovieStarType' => array(
'className' => 'MovieStarType',
'foreignKey' => 'movie_star_type_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
);
public $hasMany = array(
'MovieStarLine' => array(
'className' => 'MovieStarLine',
'foreignKey' => 'movie_star_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => 'status_date',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
),
);
I am searching and filtering within the related model Movie and the User that Movie belongsTo.
public $filterArgs = array(
'parent_user_id' => array('type' => 'like', 'field' => 'User.parent_user_id'),
'initials' => array('type' => 'value', 'field' => 'User.id'),
'trace' => array('type' => 'like', 'field' => 'MovieStar.trace'),
'movie' => array('type' => 'query', 'method' => 'orConditionsMovie'),
'star_type' => array('type' => 'value', 'field' => 'MovieStar.movie_star_type_id'),
'code' => array('type' => 'value', 'field' => 'MovieStar.code'),
'status' => array('type' => 'value', 'field' => 'MovieStarLine.movie_star_status_id'),
'open' => array('type' => 'value', 'field' => 'MovieStar.open'),
'from_date' => array('type' => 'query', 'method' => 'orConditionsFromDate'),
'end_date' => array('type' => 'query', 'method' => 'orConditionsEndDate'),
);
In order to access these models at the top level of my search, I unbind the Movie model belongsTo association and rebind it as hasOne. I do the same for MovieStarLine and MovieStarStatus.
Controller:
$this->MovieStar->recursive = 0;
$this->MovieStar->unbindModel( array(
'belongsTo' => array('Movie')
)
);
// MovieStarStatus to be top level json element
$this->MovieStar->bindModel( array(
'hasOne' => array(
'Movie' => array(
'foreignKey' => false,
'conditions' => array('Movie.id = MovieStar.movie_id'),
'fields' => array('Movie.id', 'Movie.movie_id', 'Movie.user_id', 'Movie.movie_mid', 'Movie.movie_dba')
),
'User' => array(
'foreignKey' => false,
'conditions' => array('Movie.user_id = User.id'),
'fields' => array('User.id', 'User.initials', 'User.user_first_name', 'User.user_last_name', 'User.name', 'User.parent_user_id', 'User.secondary_parent_user_id')
),
'MovieStarLine' => array(
'foreignKey' => false,
'conditions' => array('MovieStarLine.movie_star_id = MovieStar.id',
'MovieStarLine.id = (SELECT id FROM "public"."movie_star_lines" AS "MovieStarLine" WHERE movie_star_id = MovieStar.id ORDER BY status_date DESC NULLS LAST, id DESC LIMIT 1)' // Get only most recent line
),
'order' => array( 'MovieStarLine.status_date' => 'DESC NULLS LAST')
),
'MovieStarStatus' => array(
'foreignKey' => false,
'conditions' => array('MovieStarLine.movie_star_status_id = MovieStarStatus.id'),
),
),
));
$this->MovieStar->contain(array(
'Movie',
'MovieStarLine' => array('MovieStarStatus'),
'MovieStarType',
'MovieStarRecurrance',
'User',
'MovieStarStatus'
));
// This is where the search filter occurs
$this->Prg->commonProcess();
$this->paginate = array( 'conditions' => $this->MovieStar->parseCriteria($this->Prg->parsedParams()), 'limit' => 100);
$movieStars = $this->paginate();
$this->set(compact('movieStars'));
}
The problem is when I try to search by parent_user_id or initials, I get an ERROR: missing FROM-clause entry for table "User".
It seems like the dynamic binding doesn't work. I have to remove Movie from belongsTo
Model:
public $belongsTo = array(
//'Movie' => array(
// 'className' => 'Movie',
// 'foreignKey' => 'movie_id',
// 'conditions' => '',
// 'fields' => '',
// 'order' => ''
//),
'MovieStarRecurrance' => array(
'className' => 'MovieStarRecurrance',
'foreignKey' => 'movie_star_recurrance_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'MovieStarType' => array(
'className' => 'MovieStarType',
'foreignKey' => 'movie_star_type_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
);
and add hasOne for Movie and User for the search to work.
public $hasOne = array(
'Movie' => array(
'foreignKey' => false,
'conditions' => array('Movie.id = MovieStar.movie_id'),
'fields' => array('Movie.id', 'Movie.movie_id', 'Movie.user_id', 'Movie.movie_mid', 'Movie.movie_dba')
),
'User' => array(
'foreignKey' => false,
'conditions' => array('Movie.user_id = User.id'),
'fields' => array('User.id', 'User.initials', 'User.user_first_name', 'User.user_last_name', 'User.name', 'User.parent_user_id', 'User.secondary_parent_user_id')
),
'MovieStarLine' => array(
'foreignKey' => false,
'conditions' => array('MovieStarLine.movie_star_id = MovieStar.id',
'MovieStarLine.id = (SELECT id FROM "public"."movie_star_lines" AS "MovieStarLine" WHERE movie_star_id = MovieStar.id ORDER BY status_date DESC NULLS LAST, id DESC LIMIT 1)' // Get only most recent line
),
//'order' => array( 'MovieStarLine.status_date' => 'DESC NULLS LAST')
),
);
I can't do this though because it breaks all of my save and edit functionality. Does anyone know how to properly rebind the associations?
##################### UPDATE
I found a partial solution that seemingly gets me past the User model not showing up.
"Passing false as a second parameter of your Model::bindModel call. This will make your on-the-fly binding last the duration of the request." from this answer..
This might have fixed my bindings, still not sure because now I have an error with count and group by.
ERROR: column "MovieStarLine.status_date" must appear in the GROUP BY clause or be used in an aggregate
SQL Query: SELECT COUNT(*) AS "count"
$this->MovieStar->unbindModel( array(
'belongsTo' => array('Movie')
), false
);
// MovieStarStatus to be top level json element
$this->MovieStar->bindModel( array(
'hasOne' => array(
'Movie' => array(
'foreignKey' => false,
'conditions' => array('Movie.id = MovieStar.movie_id'),
'fields' => array('Movie.id', 'Movie.movie_id', 'Movie.user_id', 'Movie.movie_mid', 'Movie.movie_dba')
),
'User' => array(
'foreignKey' => false,
'conditions' => array('Movie.user_id = User.id'),
'fields' => array('User.id', 'User.initials', 'User.user_first_name', 'User.user_last_name', 'User.name', 'User.parent_user_id', 'User.secondary_parent_user_id')
),
'MovieStarLine' => array(
'foreignKey' => false,
'conditions' => array('MovieStarLine.movie_star_id = MovieStar.id',
'MovieStarLine.id = (SELECT id FROM "public"."movie_star_lines" AS "MovieStarLine" WHERE movie_star_id = MovieStar.id ORDER BY status_date DESC NULLS LAST, id DESC LIMIT 1)' // Get only most recent line
),
'order' => array( 'MovieStarLine.status_date' => 'DESC NULLS LAST')
),
'MovieStarStatus' => array(
'foreignKey' => false,
'conditions' => array('MovieStarLine.movie_star_status_id = MovieStarStatus.id'),
),
),
), false);

Creating left joins in cakephp

I am trying to select data using LEFT join in cakephp, here how can i use more than two tables for LEFT join.Here iam creating LEFT join between two tables news_feeds and news_feed_likes.i want to introduce two more tables here, NewsFeedComments and newsfeed_likes_comments.How can i do this?
Thanks for your help
$this->paginate = array(
'conditions' => array('NewsFeed.group_id'=>$groupdata['Group']['id'],'NewsFeed.status'=>'A'),
'joins' => array(
array(
'alias' => 'newslikes',
'table' => 'news_feed_likes',
'type' => 'LEFT',
'conditions' => array(
'newslikes.news_feed_id = NewsFeed.id',
'newslikes.user_id'=>$this->Auth->user('id'),
'newslikes.status'=>'1',
),
),
array(
'alias' => 'newscommentslikes',
'table' => 'news_feed_comments_likes',
'type' => 'LEFT',
'conditions' => array(
'newscommentslikes.news_feed_comment_id = NewsFeedComment.id',
'newscommentslikes.user_id'=>$this->Auth->user('id'),
'newscommentslikes.status'=>'1',
),
),
),
'fields' => array(
'IFNULL(newslikes.user_id,0) AS likestatus',
'NewsFeed.id',
'NewsFeed.posted_message',
'NewsFeed.created',
'NewsFeed.user_id',
'NewsFeed.status',
'NewsFeed.newslike',
'IFNULL(newslikes.status,0) AS status',
),
'order' => array('NewsFeed.created' => 'desc'),
);
$this->set('newsfeed', $this->paginate( $this->NewsFeed ) );
My first bit of advice would be to consider moving this to the the model. However, to answer the specific question, just add more joins based on the foreign key. So, for example, if you have a foreign key between NewsFeedLike and NewsFeedLikeComments, you can just add another join like so:
'joins' => array(
array(
'table' => 'news_feed_like_comments',
'alias' => 'NewsFeedLikeComment',
'type' => 'LEFT',
'conditions' => array(
'NewsFeedLikeComment.news_feed_like_id = NewsFeedLike.id',
),
),
Because you are already joining NewsFeedLike, it will use those conditions to limit the NewsFeedLikeComment.
Another tip: Always follow the CakePHP coding standards when writing your code. It will make your life a lot less painful. Avoid using lowercase underscored aliases for your models. Always write them CamelCase with a capital first letter and singular. So in your joins, instead of newslikes and newscommentslikes as aliases, you should use NewsLike and NewsCommentLike.
You can join multiple tables using below syntax:
$this->paginate = array(
'conditions' => array(),
'joins' => array(
array(
'alias' => 'TableAlias1',
'table' => 'table_name_1',
'type' => 'LEFT',
'conditions' => array(
'TableName.id = table_name_1.id',
),
),
array(
'alias' => 'TableAlias2',
'table' => 'table_name_2',
'type' => 'LEFT',
'conditions' => array(
'TableName.id = table_name_2.id',
),
),
array(
'alias' => 'TableAlias3',
'table' => 'table_name_3',
'type' => 'LEFT',
'conditions' => array(
'TableName.id = table_name_4.id',
),
)
),
'fields' => array(),
'order' => array()
);

cakephp habtm nested data

I have a HABTM relationship and I'd like to get nested data for a categories table from a product table something like this product array:
array(
(int) 0 => array(
'id' => (int) 1,
'name' => 'a',
'categories' => array(
(int) 0 => array(
'id' => (int) 1
),
(int) 1 => array(
'id' => (int) 2
)
)
),
(int) 1 => array(
'id' => (int) 2,
'name' => 'b',
'categories' => array(
(int) 0 => array(
'id' => (int) 1
)
)
)
)
Is this possible with cake?
EDIT: I will try to explain further what I want to do.
Please bear with me, I am new to cake and am having trouble getting to grips it.
I have a 'product' table:
public $hasAndBelongsToMany = array(
'Category' => array(
'className' => 'Category',
'joinTable' => 'categories_sculptures',
'foreignKey' => 'sculpture_id',
'associationForeignKey' => 'category_id',
'unique' => 'keepExisting'
)
)
and a category table:
public $hasAndBelongsToMany = array(
'Sculpture' => array(
'className' => 'Sculpture',
'joinTable' => 'categories_sculptures',
'foreignKey' => 'category_id',
'associationForeignKey' => 'sculpture_id',
'unique' => 'keepExisting'
)
);
and a category products table:
public $belongsTo = array(
'Category' => array(
'className' => 'Category',
'foreignKey' => 'category_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Sculpture' => array(
'className' => 'Sculpture',
'foreignKey' => 'sculpture_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
I would like to build a view table of the sculptures and also show in each row which categories each sculpture is in (which could be more than one). As I am new to cake I don't know how I would go about this. If I was just querying mysql I could get this information with a group_concat or a nested select, or inefficiently by looping through first array and querying the category_sculpture table by sculpture key and adding the results to the first array. But I would like to know the best way to get this result the cake way.
I have found the answer to my needs:
$this->Sculpture->recursive = 1;

Find conditions with hasMany model

I have 4 model:
Item------hasMany---->Detail
Item------hasMany---->Favourite
Item------hasMany---->Category
How can I find all Item that has Favourite.member_id = '8' and Category.item_category_id = '1'
Here is my Item model relation:
public $hasMany = array(
'Detail' => array(
'className' => 'Detail',
'foreignKey' => 'item_id',
'dependent' => true,
'conditions' => '',
'order' => 'created DESC',
),
'Category' => array(
'className' => 'Category',
'foreignKey' => 'item_id',
'dependent' => true,
'conditions' => '',
'order' => 'created DESC',
),
'Favorite' => array(
'className' => 'Favorite',
'foreignKey' => 'item_id',
'dependent' => true,
'conditions' => '',
'order' => 'created DESC',
)
);
I used this query() and it's ok:
$this->set('test',$this->Item->query("SELECT items.*
FROM items, categories, favourites
WHERE items.id = items_item_categories.item_id
AND items.id = favorites.item_id
AND categories.item_category_id = 1 AND favourites.member_id = 8"));
But I don't like using query(), and I change it into find(), and it seems not good:
$this->set('test',$this->Item->find('all', array(
'contain'=>array(
'ItemDetail',
'Category'=>array('conditions'=>array('Category.item_category_id'=>1)),
'Favorite'=>array('conditions'=>array('Favorite.member_id'=>8)));
You need call the containableBehaivor on your model
<?php
class Item extends AppModel {
public $actsAs = array('Containable');
}
in your controller you can make the query
$items = $this->Item->find('all', array(
'contain'=>array(
'ItemDetail',
'Category'=>array(
'conditions'=>array('Category.item_category_id'=>1)
),
'Favorite'=>array(
'conditions'=>array('Favorite.member_id'=>8)
)
)
);
$this->set('test', $items);
try using Joins
$items = $this->Item->find('all', array(
'joins' => array(
array(
'table' => 'categories',
'alias' => 'Category',
'type' => 'inner',
'conditions'=> array('Category.item_category_id' => 1)
),
array(
'table' => 'favorites',
'alias' => 'Favorite',
'type' => 'inner',
'conditions'=> array('Favorite.member_id' => 8, 'Item.id = Favorite.item_id')
)
),
'contain'=>array('ItemDetail','Category','Favorite')
);
$this->set('test', $items);
You could also try something like this:
$this->Item->hasMany['Favorite']['conditions']['member_id'] = 8;
Which has the same effect as rebinding the model with the condition.
Just a possible issue. The above will add the condition for the rest of the request, if you want to rollback to the previous behavior, you need to unset the condition:
unset($this->Item->hasMany['Favorite']['conditions']['member_id']);
Remember to set the $this->Item->recursive property to the desired value (recursive attribute). The default it's 1, which it's the minimum you need for this to work.

Resources