find() based on conditions in multiple HABTM relations - cakephp

I'm working with CakePHP 2.1
Let's state I have the following models and relations:
Posts belongsTo Edition
Posts HABTM Editors
Posts HABTM Tags
I'm reasoning from the PostsController and I would like to find all Posts belonging to a certain Edition, and that Editor.id=55 is related and Tag.id=33 is related.
I have found many examples where finding Posts based on a condition over a HABTM relation is done by 'reversing' the find direction and reason from the Editor.
$this->Editor->find('all',
array(
'conditions'=>array('Editor.id'=>55),
'contain'=>array('Post')
)
);
Unfortunately this doesn't work here, because there are multiple HABTM relations that I would like to put a condition on.
Also using contain does not work since it only cuts off a branch (editors) but does not help filtering it's parent (posts).
How do I solve this?
Do I maybe need to resort to ad hoc joins? And if so, could someone explain me roughly what approach to take?
Edit:
Some pseudo code in order to illustrate what I want to achieve:
$this->Post->find('all',
array('conditions'=>array(
'Edition.id'=>4,
// start pseudo code
'Post.Editor' => 'has at least one related editor with id=55',
'Post.Tag' => 'has at least one related tag with id=33',
)
)
);
kind regards,
Bart
Edit Solution:
Following #RichardAtHome I created the solution below. Not (yet) employing multiple join conditions, but that appeared not to be necessary:
// Paginate over Cards that belong to current Subscription (belongsTo) AND the User is related to as an Editor (HABTM)
$this->paginate = array(
'fields' => array('id','title','workingtitle','attachment_count','subscription_id'),
'joins' => array(
// create Editor-join
array(
'table' => 'cards_users',
'alias' => 'Editor',
'type' => 'left',
'conditions' => array(
'Editor.card_id = Card.id'
)
),
array(
'table' => 'users',
'alias' => 'User',
'type' => 'left',
'conditions'=> array(
'User.id = Editor.user_id'
)
)
),
'conditions' => array(
'OR' => array(
// is Card in current subscription? (straightforward condition)
array('Card.subscription_id',$subscription_id),
// is current User assigned as Editor? (condition works with join above)
array('User.id' => $user['id'])
)
),
'limit' => 10
);

For this type of query, I usually find it easiest to construct the joins myself as cake won't join, it will perform multiple queries instead (select matching rows from table a, then fetch matching rows from table b).
Your query needs to look something like this:
$this->Post->find('all',
array(
'conditions'=>array('Post.edition_id'=>4),
'joins'=>array(
array(
'type'=>'inner',
'table'=>'posts_editors',
'alias'=>'PostsEditor',
'conditions'=>array(
'PostsEditor.post_id = Post.id',
'PostsEditor.editor_id'=>55 // your criteia for editor id=55
)
),
array(
'type'=>'inner',
'table'='posts_tags',
'alias'=>'PostsTag',
'conditions'=>array(
'PostsTag.post_id = Post.id',
'PostsTag.tag_id'=>33 // your criteria for tag id = 33
)
)
)
)
);
Check the SQL that's output to see what this query is actually doing behind the scenes.

Try:
$this->Post->find('all',
array('conditions'=>array(
'Edition.id'=>4,
'Editor.id' => '55',
'Tag.id' => '33',
)
)
);

Related

CakePHP belongsto like %%

i need some help with these, I can't seem to be able find anything on internet.
I have a model
public $belongsTo = array(
'Group' => array(
'className' => 'Group',
'foreignKey' => 'group_id',
),
'Audit_type' => array(
'className' => 'Audit_type',
'foreignKey' => 'audit_type',
),
'User' => array(
'className' => 'User',
'foreignKey' => 'auditor',
),
);
but I need to be able to find Group with a like, because now in group_id i have multiple id like 145,125,123
is there any way i can find it with a LIKE??
Update:
the table groups is a tree behavior table, is tree behavior from cakephp
now the table that has multiple ids, is a table that has audits, and i need this audit to be part of multiple groups.
DON'T store multiple id's in the group_id field!
Rather than a BelongsTo relationship, it sounds like you need CakePHP's Has And Belongs To Many relationship.
But whatever you do, don't store multiple id's in the one foreign key field. That would be enough to give Codd a heart attack!
You shouldn't use a "LIKE" for finding multiple IDs.
Do it like this instead:
$this->Group->find('all', array(
'conditions' => array(
'id' => array('145', '125', '123')
)
);
It will use MySQLs "IN".

Is there a way to define Model relationships with conditions in CakePHP?

I am using CakePHP 1.3.
I have a few questions regarding CakePHP Models:
1) Is there a way to define a Model with conditions, such that when invoked using the Containable, behavior, I do not need to define 'conditions' for it again. For example: I have two models - "Store" and "Deal" where "Store" that hasMany "Deal". I want to accomplish the following without having to define these conditions for "Deal" every time:
$this->Store('all', array('contain'=>array('Deal'=>array('isactive'=>1,'now() < expirydate', 'qty > 0')));
2) Also, is there a way to define relationships between Models differently based on conditions? In other words, how can I define "Store" hasMany "Deals", "ActiveDeal", "ExpiredDeal", etc.., all on the 'deals' table but differing based on conditions I set for each.
Much appreciate any help.
Thanks/Regards..
If you look at the first couple of code examples on the Associations: Linking Models Together page of the CakePHP Cookbook, you'll see that you can add conditions to your model joins that will work whenever you do a basic find.
Therefore you should be able to do something like the following:
class Store extends AppModel {
var $name = 'Store';
var $hasMany = array(
'Deal' => array(
'className' => 'Deal',
'foreignKey' => 'store_id',
'conditions' => array(
'Deal.isactive' => '1',
'now() < Deal.expirydate',
'Deal.qty > 0'
),
'order' => ''
),
'ExpiredDeal' => array(
'className' => 'Deal',
'foreignKey' => 'store_id',
'conditions' => array('now() >= ExpiredDeal.expirydate'),
'order' => ''
)
);
}

CakePHP: How can I customize the labels for multiple checkboxes

I've done a lot of searching but havent been able to solve this yet. I have a user registration form where the user can select a number of store branches as favourites upon registration. I have this relationship set up as a HABTM.
I am able to display the store branches as multiple checkboxes, but I need to display the store name (the store branches belongs to store names) along with the branch name in the label for each checkbox. Something like:
Levi's - Canal Walk
where Canal Walk is the branch name, and Levi's is the store name (coming from a different table)
Can anyone please share some insight on how to do this?
I haven't tested this, but it might work (or help you along the right path):
$branches = $this->Branch->find('list', array(
'fields' => array('Branch.id', 'CONCAT(Store.name, " - ", Branch.name)'),
'joins' => array(
array(
'table' => 'stores',
'alias' => 'Store',
'type' => 'inner',
'foreignKey' => false,
'conditions' => array('Branch.store_id' => 'Store.id')
)
)
));
Set this in your controller, or keep things DRY by placing it in your model in a function such as findFullList()
Hope that helps!
I would do all of this in the view. If your HABTM relationship is setup correctly, a query of the Store model should work something like this:
$stores = $this->Store->findAll();
//Returns
Array([0] => Array(
'Store' => array(),
'Branch' => array(
[0] => array(),
[1] => array()...));
Then pass the $stores variable into the view, and iterate through it with a double nested loop.
foreach($stores as $store){
foreach($store['Branch'] as $branch){
//Code to output checkbox by using $branch['name']
}
}

Is it possible to use bindModel to bind 3 different nested tables in CakePHP

I have a segment which can have many comments and each comment can have many tags. I can bind the comments to the segments using code like the below which is a function in the segment model class.
function prepareForGettingSegmentsWithComments() {
$this->bindModel(
array('hasMany' => array(
'Comment' => array(
'className' => 'Comment',
'foreignKey' => 'segmentID'
)
)
)
);
}
However how can I bind in the Tags as well?
Yes, in this situation, I bind the Tags to the Comments with a belongsTo. Then filter the results with some query conditions.
Let me see if I can find an example snippet somewhere,
if(isset($this->params['named']['category'])){
$this->Link->bindModel(
array('belongsTo' => array(
'CategoriesLink' => array(
'className' => 'CategoriesLink',
'foreignKey' => 'id',
)
)),
array('belongsTo' => array(
'Category' => array(
'className' => 'Category',
'foreignKey' => 'categories_link_id',
)
))
);
$data = $this->paginate('Link', array('CategoriesLink.category_id'=>$this->params['named']['category']));
} else {
$data = $this->paginate('Link', array('Link.status_id'=>'1'));
}
$this->set('links', $data);
This is how I did it when I was trying to paginate my Link model by a related field. This was with Cake1.2 though, but I think the principle is the same.
I would also recommend installing DebugKit, http://www.ohloh.net/p/cakephp-debugkit , then tinker with the links and conditions until you get a query which works for you.
Sorry, not very technical ;) I'm sure someone can give you a more accurate answer.
PS, Having just reread the question, do you not have these Models linked already? Surely hooking them up in the Models through CakePHP relationships, you'd not need to bind the models and could just use Containable or unbindModel()

How do I write a join query across multiple tables in CakePHP?

can anyone tell me, how to retrieve joined result from multiple tables in cakePHP ( using cakePHP mvc architecture). For example, I have three tables to join (tbl_topics, tbl_items, tbl_votes. Their relationship is defined as following: a topic can have many items and an item can have many votes. Now I want to retrieve a list of topics with the count of all votes on all items for each topic. The SQL query for this is written below:
SELECT Topic.*, count(Vote.id) voteCount
FROM
tbl_topics AS Topic
LEFT OUTER JOIN tbl_items AS Item
ON (Topic.id = Item.topic_id)
LEFT OUTER JOIN tbl_votes AS Vote
ON (Item.id = Vote.item_id);
My problem is I can do it easily using $this-><Model Name>->query function, but this requires sql code to be written in the controller which I don't want. I'm trying to find out any other way to do this (like find()).
$markers = $this->Marker->find('all', array('joins' => array(
array(
'table' => 'markers_tags',
'alias' => 'MarkersTag',
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array('MarkersTag.marker_id = Marker.id')
),
array(
'table' => 'tags',
'alias' => 'Tag',
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array(
'Tag.id = MarkersTag.tag_id',
'Tag.tag' => explode(' ', $this->params['url']['q'])
)
)
)));
as referred to in nate abele's article: link text
I'll be honest here and say that you'll probably be a lot happier if you just create a function in your model, something like getTopicVotes() and calling query() there. Every other solution I can think of will only make it more complicated and therefore uglier.
Edit:
Depending on the size of your data, and assuming you've set up your model relations properly (Topic hasMany Items hasMany Votes), you could do a simple find('all') containing all the items and votes, and then do something like this:
foreach ($this->data as &$topic)
{
$votes = Set::extract('/Topic/Item/Vote', $topic);
$topic['Topic']['vote_count'] = count($votes);
}
Two things are important here:
If you have a lot of data, you should probably forget about this approach, it will be slow as hell.
I've written this from my memory and it might not look like this in real life and/or it may not work at all :-)
You can easily set the "recursive" property on a find() query.
$result = $this->Topic->find('all', array('recursive' => 2));
Alternatively, you can use the Containable behavior in your model. Then you can use:
$this->Topic->contain(array(
'Item',
'Item.Vote',
));
$result = $this->Topic->find('all');
or
$result = $this->Topic->find('all', array(
'contain' => array(
'Item',
'Item.Vote',
),
));
What you need is recursive associations support, which is not possible with stock CakePHP currently.
Although it could be achieved using some bindModel trickery
or an experimental RecursiveAssociationBehavior.
Both of these solutions will either require you to use extra code or rely on a behaviour in your application but if you resist the temptation to write pure SQL code, you'll be rewarded with being able to use Cake`s pagination, auto conditions, model magic etc..
I think this answer is already submitted, but I am posting here for someone who seeks still for this.
The joins can be done with find() method can be like below
$result = $this->ModelName1->find("all",array(
'fields' => array('ModelName1.field_name','Table2.field_names'), // retrieving fileds
'joins' => array( // join array
array(
'table' => 'table_name',
'alias' => 'Table2',
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array('ModelName1.id = Table2.id') // joins conditions array
),
array(
'table' => 'table_name3',
'alias' => 'Table3',
'type' => 'inner',
'foreignKey' => false,
'conditions'=> array('Table3.id = Table2.id')
)
)));
You should study HaBTM (Has and Belongs to Many)
http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html

Resources