Laravel - How to search based on tags in many-to-many polymorphic relation?
-
21-12-2019 - |
Question
I have a form set up with a text input and a multi-select input. The multi-select input has a list of tags. The tags are set up as a many-to-many polymorphic relation. To help make things clear, below is an outline of my tables and models:
Tables:
org
id - integer
name - text
...
tags
id - integer
name - text
taggables
tag_id - integer
taggable_id - integer
taggable_type - text
Models:
class Org extends Eloquent
{
...
public function tags()
{
return $this->morphToMany('Tag', 'taggable');
}
}
class Tag extends Eloquent
{
...
public function org()
{
return $this->morphedByMany('Org', 'taggable');
}
}
In my routes.php file, I am trying to perform a search like so...
$search = Input::get('keyword');
$searchTerms = explode(' ', $search);
$query = Org::query();
$fields = array('name', 'description', 'additional_info', 'website');
foreach ($searchTerms as $term)
{
foreach ($fields as $field)
{
$query->orWhere($field, 'LIKE', '%'. $term .'%');
}
}
if(Input::get('tags'))
{
$tags = Input::get('tags');
$query
->join('taggables', 'taggables.taggable_id', '=', 'org.id')
->where('taggables.taggable_type', '=', 'Org')
->join('tags', 'taggables.tag_id', '=', 'tags.id')
->groupBy('org.id');
foreach ($tags as $tag)
{
$query->orWhere('tags.id', '=', $tag);
}
}
$org = $query->get();
However, this of course doesn't work. The keyword search works fine, but I am not sure how to add a query that will retrieve only the rows that have the specified associated tags. The $tags variable is an array containing the IDs of tags.
I want to return only the rows from the org table that have at least one of the tags specified in the array.
UPDATE: I have updated my code, and it kind of works, but it seems to return tags and not the associated organizations - though I feel I'm getting close. Here is the relevant code I added (or just look at it above):
$query
->join('taggables', 'taggables.taggable_id', '=', 'org.id')
->join('tags', 'taggables.tag_id', '=', 'tags.id')
->where('taggables.taggable_type', '=', 'Org')
->whereIn('tags.id', $tags);
UPDATE 2: This seems to work how I want it, but for some reason, it retrieves the name of the tag instead of the organization name...
$tags = Input::get('tags');
$query
->join('taggables', 'taggables.taggable_id', '=', 'org.id')
->where('taggables.taggable_type', '=', 'Org')
->join('tags', 'taggables.tag_id', '=', 'tags.id')
->groupBy('org.id');
foreach ($tags as $tag)
{
$query->orWhere('tags.id', '=', $tag);
}
Solution
I got the keyword + tag search working in the following way:
$query = Org::query();
// Search by keyword(s)
if(Input::get('keyword'))
{
$search = Input::get('keyword');
$searchTerms = explode(' ', $search);
$fields = array('org.name', 'org.description', 'org.additional_info', 'org.website');
foreach ($searchTerms as $term)
{
foreach ($fields as $field)
{
$query->orWhere($field, 'LIKE', '%'. $term .'%');
}
}
}
// Search for tag(s)
if(Input::get('tags'))
{
$tags = Input::get('tags');
$query
->join('taggables', 'taggables.taggable_id', '=', 'org.id')
->where('taggables.taggable_type', '=', 'Org')
->whereIn('taggables.tag_id', $tags)
->groupBy('org.id')
->orderBy(DB::raw('count(*)'), 'desc')
->orderBy('name', 'asc');
}
$org = $query->get();
If a keyword and two tags are entered for the search (for example), this will return those organizations which have one or more of the tags, contain words which are like the entered keyword, and sort the results so that organizations with the most matching tags will appear first.