Reputation: 14666
In Laravel 4 I have the following tables
- items table
--- id
--- name
- tags table
--- id
--- name
- item_tag
--- id
--- tag_id
--- item_id
--- created_at
--- updated_at
class Item extends Eloquent {
public function tags()
{
return $this->belongsToMany('Tag');
}
}
class Tag extends Eloquent {
public function items()
{
return $this->hasMany('Item');
}
}
my question:
I want to get all items that have the following two tags "foo" and "bar"? only items that have both tags should be returned!?
I have tried the below but it did not work for me, I have a feeling that the problem is with the "->having" clause but I could not get it right,
lets asume that the tag "foo" has the id 1 and "bar" has id 2
class Item extends Eloquent {
protected $table = 'items';
public function tags()
{
return $this->belongsToMany('Tag');
}
public static function withTags()
{
return static::leftJoin(
'item_tag',
'items.id', '=', 'item_tag.item_id'
)
->whereIn('item_tag.tag_id', array(1, 2))
->groupBy('items.id')
->having('count(*)', '=',2)
;
}
}
and to run it
#routes.php
Route::get('/', function()
{
return Item::withTags()->get();
});
it should return all items with that tags 1 and 2 however it is not returning anything!
any help?
Upvotes: 2
Views: 2910
Reputation: 14666
Finally I have found the answer!
using "havingRaw" will solve the problem
also "lists" give us the IDs of the tags from tags table
note: "havingRaw" is available only in Laravel 4 -beta 4- or later
class Item extends Eloquent {
protected $table = 'items';
public function tags()
{
return $this->belongsToMany('Tag');
}
public static function withTags($tags = array())
{
$count = count($tags);
return static::leftjoin('item_tag', 'items.id', '=', 'item_tag.item_id')
->whereIn('item_tag.tag_id', Tag::whereIn('name', $tags)->lists('id'))
->groupBy('item_tag.item_id')
->havingRaw('count(*)='.$count)
;
}
}
and to run it
return Item::withTags(['foo','bar'])->get();
Update:Important Note
when you see the output of the code above you will notice that item->id does not contain the items.id!, instead it will contain the tags.id, this is because using "joins" will cause ambiguity, to solve this you have to add the following select statement
->select('items.id as id','items.name as name')
Upvotes: 4
Reputation: 161
I guess you are looking for a inner join? try this:
class Item extends Eloquent {
protected $table = 'items';
public function tags()
{
return $this->belongsToMany('Tag');
}
public static function withTags()
{
return static::join('item_tag', 'items.id', '=', 'item_tag.item_id')
->whereIn('item_tag.tag_id', array(1, 2));
}
}
Upvotes: 1
Reputation: 1167
For a ManyToMany relationship, both Eloquent classes need to return a $this->belongsToMany
. In your Tag class you are using hasMany which does not use the pivot table.
If you fix the above, then you should be able to properly access the pivot table.
I think the problem is that you need to join on the tags id instead of the items id.
return static::leftJoin(
'item_tag',
'tags.id', '=', 'item_tag.tag_id'
)
Upvotes: 1