CakePHP - find Model using association other than id - cakephp

I'm having no luck trying to bend CakePHP to do what I need.
I have 2 Models, Listing & ListingService.
ListingService's fields are as follow: id, title, listing_id, service_type_id
(So in a way it is like a has and belongs to many relationship with Listing)
When I do a find right now. (Using belongsTo in Listing.php)
public $belongsTo = array(
'ListingService' => array(
'className' => 'ListingService',
'foreignKey' => 'id'
)
);
$this->paginate = array('type' => 'all', 'recursive' => 0,
'fields' => array('Listing.id', 'Listing.title',
'ListingService.service_type_id','ListingService.title'),
'limit' => 10
);
This is the query that is showing
SELECT `Listing`.`id`, `Listing`.`title`, `ListingService`.`service_type_id`, `ListingService`.`title` FROM `listings` AS `Listing` LEFT JOIN `listing_services` AS `ListingService` ON (`Listing`.`id` = `ListingService`.`id`) LIMIT 10
This is almost what I need, except that I would like the LEFT JOIN to be ON (Listing.id = ListingService.listing_id) instead of ON (Listing.id = ListingService.id)
If I use "hasMany" it doesn't even do the LEFT JOIN.
I don't have a ListingService.php model currently.
Please enlight.
Thank you,
Tee

Found a solution.
I need to first do this.
$this->Listing->ListingService->primaryKey = 'listing_id';
Is this a hack? If yes, is there a better way to do it?

I think your belongsTo should be like this
public $belongsTo = array(
'ListingService' => array(
'className' => 'ListingService',
'foreignKey' => 'listing_id'
)
);

Related

Select records which have atleast one hasMany relation row in Cakephp

I'm facing a problem with cakephp associations in Models.
I have to Select records which have atleast one hasMany reation row
Model
class Category extends AppModel
{
public $hasMany = array(
'Product' => array(
'className' => 'Product',
'foreignKey' => 'CategoryId',
)
);
}
Query
$categories = $this->Category->find('all');
I only needed the categories which have atleast one product entry
Categories Like : Shirts, Footwear, Glasses etc
Products like :
Small, medium, large (Shirts)
With Frame, UV protected (Glass)
So, i jus want to get Shirts and Glasses Categories only because for the above example there is no products for Footwear
Use counterCache or joins
Please refer to CakePHP - Find and count how many associated records exist
The most simple way with the best performance would be using a properly indexed counter cache field as shown in the linked answer.
Sice the linked answer is not an exact duplicate with respect to the join, here's some additional info, instead of using HAVING COUNT with the join you'd use a IS NOT NULL condition. Here's an (untested) example:
$this->Category->find('all', array(
'joins' => array(
array(
'table' => 'products',
'alias' => 'Product',
'type' => 'LEFT',
'conditions' => array('Category.id = Product.CategoryId')
)
),
'conditions' => array(
'Product.CategoryId IS NOT NULL'
)
'group' => 'Category.id'
));
Depending on the used DBMS and version you might get better performance using an inner join:
$this->Category->find('all', array(
'joins' => array(
array(
'table' => 'products',
'alias' => 'Product',
'type' => 'INNER',
'conditions' => array('Category.id = Product.CategoryId')
)
),
'group' => 'Category.id'
));

HABTM Find with CakePHP 2.0

I am trying to do a search, using pagination for posts which have a specific tag or tags (for example, if a user was to select two tags, then posts containing either tag would be returned).
I have the relationship defined in my Posts table
public $hasAndBelongsToMany = array('Tags' => array(
'className' => 'Tags',
'joinTable' => 'posts_tags',
'foreignKey' => 'post_id',
'associationForeignKey' => 'tag_id',
'unique' => 'keepExisting'));
How do I use Find to retrieve rows with a given tag (name or ID would be fine)
Trying:
// other pagination settings goes here
$this->paginate['conditions']['Tags.id'] = 13;
gives me an error that the relationship does not exist.
Looking at the debug info it appears that the tables are not joining the Posts_Tags and Tags table, however, when I debug the data making it to the view, the Posts objects contain the tags data.
Most of the documentation I can find for this seems to revolve around earlier versions of CakePHP, any help would be appreciated.
Could not find a satisfying solution myself.
I created a behavior to take care of this.
Create a file called HabtmBehavior.php and put it in your app/Model/Behavior folder.
Put the block of code in there and save file.
Add the behavior to your model: eg public $actsAs = array('Habtm');
Here is a usage example with find.
<?php $this->Entry->find('all', array('habtm'=>array('Tag'=>array('Tag.title'=>'value to find'))) ?>
Paginate would look something like this:
$this->paginate['Entry']['habtm']['Tag'] = array('Tag.title'=>'value to find');
You are free to add as many relations as you want by adding additional Model Names in the habtm array.
(Just be careful not to make it to complex since this could start slowing down your find results.)
<?php
class HabtmBehavior extends ModelBehavior {
public function beforeFind(Model $model, $options) {
if (!isset($options['joins'])) {
$options['joins'] = array();
}
if (!isset($options['habtm'])) {
return $options;
}
$habtm = $options['habtm'];
unset($options['habtm']);
foreach($habtm as $m => $scope){
$assoc = $model->hasAndBelongsToMany[$m];
$bind = "{$assoc['with']}.{$assoc['foreignKey']} = {$model->alias}.{$model->primaryKey}";
$options['joins'][] = array(
'table' => $assoc['joinTable'],
'alias' => $assoc['with'],
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array($bind)
);
$bind = $m.'.'.$model->{$m}->primaryKey.' = ';
$bind .= "{$assoc['with']}.{$assoc['associationForeignKey']}";
$options['joins'][] = array(
'table' => $model->{$m}->table,
'alias' => $m,
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array($bind) + (array)$scope,
);
}
return $options;
}
}
Hope this helps.
Happy baking.
I think the best solution is apply find function on join table Model. I try this before and it's work fine.
in your PostTag model :
/**
* #see Model::$actsAs
*/
public $actsAs = array(
'Containable',
);
/**
* #see Model::$belongsTo
*/
public $belongsTo = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'post_id',
),
'Tags' => array(
'className' => 'Tag',
'foreignKey' => 'tag_id',
),
);
in your controller :
// $tagsId = tags ids
$posts = $this->PostTag->find('all', array('conditions' => array('PostTag.tag_id' => $tagsId),'contain' => array('Post')));
also is better follow cake naming convention, if you have tags(plural), post_tags(first singular second plural),posts(plural) tables you must have Tag,PostTag,Post Models.

cakephp threaded comments

I a bit new to cakephp and I'm wondering why I have this kind of problem.
Basically, I am doing a threaded comments in cakephp. But my problem is that every time I try to "comment" on a "comment", it is displaying differently.
Here is a screenshot:
I want it to be reversed like all the child comments should be posted on the last row. Currently, when I add a new comment, it is displayed on the top rather than on the bottom. I want it to become like how facebook does their commenting.
Here is my code for this:
$comments = $this->UserDiscussionComment->find('threaded', array('conditions' => array('UserDiscussionComment.user_discussion_id' => $slug), 'order' => array('UserDiscussionComment.id DESC', 'UserDiscussionComment.created ASC')));
And here is a sample records in the database:
I want to change the order of the child comments. I tried "ASC" and "DESC" but its not working.
Thanks in advance.
You won't be able to order the children in a different order according to the documentation for find('threaded'). What I would do is after your find call, simply reverse the array:
$comments = $this->UserDiscussionComment->find('threaded', array(
'conditions' => array('UserDiscussionComment.user_discussion_id' => $slug),
'order' => array('UserDiscussionComment.id DESC')
));
for ($i = 0; $i < sizeof($comments); $i++) {
$comments[$i]['children'] = array_reverse($comments[$i]['children']);
}
Untested but it should do the trick (also I assume you can only comment 1 level deep like your screenshot shows).
Edit
I wanted to share a different approach I used in the past for the same thing you're trying to accomplish. Basically I set my Comment model up like so:
class Comment extends AppModel {
public $belongsTo = array(
'ParentComment' => array(
'className' => 'Comment',
'foreignKey' => 'parent_id'
),
'User'
);
public $hasMany = array(
'ChildComment' => array(
'className' => 'Comment',
'foreignKey' => 'parent_id'
)
);
}
Then, when I want to do a find, I can order the parent & child comments differently (note that I'm using the Containable behavior):
$comments = $this->Comment->find('all', array(
'order' => 'Comment.id DESC',
'contain' => array(
'ChildComment' => array(
'order' => 'ChildComment.id ASC'
)
)
));
array('UserDiscussionComment.id DESC', 'UserDiscussionComment.created ASC')
I think the error is there.
As UserDiscussionComment.id are all distinct, there is nothing to order for UserDiscussionComment.created ASC.
Try:
array('UserDiscussionComment.parent_id DESC', 'UserDiscussionComment.created ASC')

CakePHP Model Relationship with Multiple Foreign Keys

In my CakePHP app I have models for Matches and Teams. Each Match has a home_team_id and an away_team_id, both of which reference a different Team.
In my team.php file, I am able to form the relationship for a Team's home matches:
var $hasMany = array(
'HomeMatch' => array('className' => 'Match', 'foreignKey' => 'home_team_id'),
'AwayMatch' => array('className' => 'Match', 'foreignKey' => 'away_team_id')
);
My problem is that I cannot automatically retrieve a Team's home and away Matches in a single array. That is, the retrieved Matches are returned in separate HomeMatch and AwayMatch arrays, which causes sorting difficulties.
I have tried the following:
var $hasMany = array(
'Match' => array('foreignKey' => array('home_team_id', 'away_team_id'))
);
...with no luck.
Any ideas on how to combine these two foreign keys into a single relationship?
Thanks, Ben
A custom finderQuery should do the trick:
public $hasMany = array(
'Match' => array(
'className' => 'Match',
'foreignKey' => false,
'finderQuery' => 'SELECT *
FROM `matches` as `Match`
WHERE `Match`.`home_team_id` = {$__cakeID__$}
OR `Match`.`away_team_id` = {$__cakeID__$}'
)
);
I was having a similar issue and instead of creating a finderQuery I used the conditions operator and it worked great!
public $hasMany = array(
'Match' => array(
'className' => 'Match',
'foreignKey' => false,
'conditions' => array(
'OR' => array(
array('Match.home_team_id' => '{$__cakeID__$}'),
array('Match.away_team_id' => '{$__cakeID__$}')
)
),
)
);
They are returned in seperate array's because the sort of represent different models (in this particular case the model is the same).
You should probably build a helper method to go over the retrieved data (in the model object or in a separate helper class) and "flatten" it. then you'd be able to sort it.
Ken.

post habtm postlover : how to order posts by number of postlovers?

please help me, i'm really struggling with this...
Authors can write post, and authors can love other authors' posts...
So posts belong to author (when authors write them), but posts habtm
authors (when authors love them).
For example i'd like to get posts ordered by number of postlovers and
created in the last 24 hours. Here's my models and join table:
TABLE: lovedposts_postlovers
id: INT
lovedpost_id: INT
postlover_id: INT
POST MODEL
<?php
class Post extends AppModel {
var $name = 'Post';
var $belongsTo = 'Author';
var $hasAndBelongsToMany = array(
'Postlover' =>
array(
'className' => 'Author',
'joinTable' => 'lovedposts_postlovers',
'foreignKey' => 'lovedpost_id',
'associationForeignKey' => 'postlover_id',
'unique' => true
)
);
var $displayField = 'title';
}
?>
AUTHOR MODEL
<?php
class Author extends AppModel {
var $name = 'Author';
var $hasMany = 'Post';
var $hasAndBelongsToMany = array(
'Lovedpost' =>
array(
'className' => 'Post',
'joinTable' => 'lovedposts_postlovers',
'foreignKey' => 'postlover_id',
'associationForeignKey' => 'lovedpost_id',
'unique' => true
)
);
var $displayField = 'username';
}
?>
Your best option is to query on the joinModel. Usually that would be:
$this->Author->AuthorsLovedpost->find(...);
But since you're not sticking to the Cake naming conventions for the table that may or may not be different. The joinModel is automatically created BTW, but you can also explicitly specify it in the HABTM declaration.
var $hasAndBelongsToMany = array(
'Postlover' => array(
...,
'with' => 'joinModelName'
)
);
For the find options you can do whatever you need, 'group' => 'post_id' and 'order' => 'COUNT(post_id)' or something to that extend. What you're looking for is getting the right set of 'post_ids' back.
Since from the point of view of the joinModel the Author and Post models are both belongsTo relationships, Cake will find related results accordingly and you can use the usual 'contain' options etc to filter results.
Hope that helps.
I think you should take a step back and try to read the documentation (book.cakephp.org). Try to make a demo using their examples.
lovedposts_postlovers table is very confusing and maybe should be called something else, maybe authors_posts or even favorites. well, it can be anything as long as you specify it in 'joinTable'.
lovedposts_postlovers should have the fields author_id, post_id
//POST MODEL
var $hasAndBelongsToMany = array(
'Author' =>
array(
'className' => 'Author',
'joinTable' => 'lovedposts_postlovers',
'foreignKey' => 'author_id',
'associationForeignKey' => 'post_id',
'unique' => true
)
);
For example i'd like to get posts ordered by number of postlovers and created in the last 24 hours. Here's my models and join table:
$this->Post->LovedPosts->find('all', array('fields'=>array('Post.title', 'count(LovedPosts.*) as favorited'), 'group'=>'LovedPosts.post_id');
Basically you want to do a select count query and group by the post_id and this code should get you on the right track. Note: I didn't test this code. You also need an order clause in that find operation but I will leave that to you.

Resources