Searching Model with Many to Many Polymorphic Relation - php

Following the tags example in the Laravel docs, I've successfully implemented image tagging, and now I'm trying to search by said tags.
Pulling an image from a single tag works well, but I'm hitting a roadblock when trying to search for images containing multiple tags.
This is the closest I've gotten, and it works as expected but it is not what I desire.
/**
* $tag string, example: 'baseball'
* $tags array, example: ['baseball','stadium','new.york.yankees']
*/
Image::whereHas('tags', function($query) use ($tag,$tags) {
if (count($tags)) {
// Retrieves images tagged as 'baseball' OR 'stadium' OR 'new.york.yankees'
// Instead, I want it to retrieve images that have all three tags
$query->whereIn('tag', $tags);
// The following returns no results, though there are images tagged correctly.
// I presume this is an incorrect approach.
foreach ($tags as $tag) {
$query->where('tag', '=', $tag);
}
} else {
// Retrieves images tagged as 'baseball'
$query->where('tag', '=', $tag);
}
})->orderBy('created_at', 'desc')
->paginate(config('app.images_per_page'))
I'm stumped, overtired, and getting nowhere while searching for similar examples. What am I missing? What is the correct terminology for what I am trying to achieve, so I can add it to my vocabulary for future endeavors?

I think this just needs to be a whereHas statement for each of the tags you are trying to filter by. This would create an AND statement within the query.
if(!$count($tags)) $tags = [$tag]; //for simplicity lets always have an array
$imageQuery = Image::query();
foreach($tags as $tag){
$imageQuery = $imageQuery->whereHas('tags', function($query) use ($tag) {
$query->where('tag', '=', $tag);
});
}
$results = $imageQuery->orderBy('created_at', 'desc')
->paginate(config('app.images_per_page'))->get();
Code is not tested but logically I think you need a where clause for each tag

Related

How to concatenate the names of every object in $tags?

When I upload an image, there's a field which takes tags in this format:
tag1,tag2,tag3
then I explode those tags on every comma and insert every separate tag in the database. Now I am trying to do the reverse thing, get all tags in $tags and then concatenate $tag->name with a comma for every tag. For example:
After getting my tags with:
$tags = Tag::whereHas('images', function($q) use ($id) {
return $q->where('taggable_id', $id);
})->get();
$tags have 2 tags with the following names, Summer and Winter. I am trying to somehow get a string that is "Summer,Winter". I'm not exactly sure how to do that though.
If you get $tags and it is Illuminate\Database\Eloquent\Collection instance you cane use this code
$tagConcateNames = $tags->implode('name', ',')
Here is example which you need.
$tags = Tag::whereHas('images', function($q) use ($id) {
return $q->where('taggable_id', $id);
})->selectRaw("GROUP_CONCAT(name) as group_names")->get();
Sometimes we need to use Raw queries to perform actions on data. Try this. I think this will give you result as you required.

Show Posts By Multiple Tags specified (Laravel 5.6)

I want to show only posts that are related to multiple tags. Posts and tags are connected with many to many relationship.
In filter menu user can check multiple tags and then only posts that are related to all of those tags needs to be shown.
For example: If user checks #fruit and #vegetable tags then posts with both of those tags will appear.
whereIn('tag_id', $array_of_tag_ids) method is working but its showing all posts that have at least one tag of those specified.
Thanks
You can use foreach with whereHas.
$tags = $request->input('tags');
$posts = Post::when($tags, function($query) use ($tags){
foreach($tags as $tag){
$query->whereHas('tags', function($query) use ($tag){
$query->where('id', $tag);
});
}
})
->get();

Laravel 5 query with variable number of where clauses

I'm try to make a tag-based search system similar to the one here on stackoverflow using Laravel and jQuery Token-Input plugin. The contents of the "tags" are then to be used as the query itself, drawn from a list from a different table.
Using Eloquent, I want to build a query based on a variable number of tags in the search bar (limited only by the number of possible tags there can be). It would look something like this if not done as a loop:
$query = Model::whereHas('attribute', 'name', '=', 'tag1')
->whereHas('attribute', 'name', '=', 'tag2')
->whereHas('attribute', 'name', '=', 'tag3')
// Repeat until...
->get();
The 'attribute' in question is actually something drawn from a pivot table.
Obviously, I want it to be in the form of a loop since we're dealing with a variable number of tags. How would I go about doing this?
I suppose you can so something like this:
$query = Model::whereHas('attribute', 'name', '=', 'tag1');
foreach ($other_tags as $tag) {
$query = $query->whereHas('attribute', 'name', '=', $tag);
}
print_r($query->get());
The only hint here is that you need to use first tag to init Model::whereHas. Other tags can be iterated over and each one will be added to $query.
If you have tags in array like this
$tags = [ 'tag1', 'tag2', 'tag3', 'tag4', ...];
then you can simply use whereIn
$results = Model::whereHas('attribute', function($query) use ($tags) {
$query->whereIn('name', $tags);
})->get();

Combining query results in laravel

On my website, users can upload images and attach tags to those images.
I've got an images table,a tag table and an images_tag pivot table.
Images can have many tags, and tags can belong to many images.
I want to be able to generate a list of all the tags a user has used in his/her images.
$imageIDs = Images::where('created_by', Auth::user()->id)->lists('id');
So this would create a list of all the image IDs that a user has upload.
What I want is essentially "foreach $imageIDs, check the images_tag table and for every match go to the tags table and get me back the tagname value."
But I have no idea how I'd do that.
Maybe a foreach then use the merge method on all the results? Any help would be appreciated!
You need to use whereHas() to check the relationship:
$userTags = Tags::whereHas('images', function($q) {
$q->where('created_by', auth()->user()->id);
})->get();
Then just pass this data to a view:
return view('some.view', compact('userTags'));
And iterate over tags in a view:
#foreach ($userTags as $tag)
{{ $tag->name }}
#endforeach
What you could do is this.
class Tag extends Model
{
public function images()
{
return $this->belongsToMany(Image::class);
}
}
class SomeController
{
public function someMethod()
{
$tags = Tag::with(['images' => function ($image) {
return $image->where('created_by', Auth::user()->id);
}])->select('id', 'tagname')->get();
// these are your $tags
}
}
You should not use a query inside foreach(). Then it would result N+1 problem. What you instead do is eager loading using with() statement.

Laravel get posts with multiple tags

I'm working with a laravel belongsToMany relationship between Post and Tag.
What I'm trying to do is get all Posts where it has multiple tags.
I've tried all sorts of eloquent queries, but I can't get it at all.
Currently I can get an array of post_id's and tag_id's, as shown below, but there has to be an easier way to do this.
if (Request::has('tags')) {
$tags = Tag::find(explode(',', Request::get('tags')));
}else{
$tags = null;
}
// Get all posts tagged with the tags
$jobs = \DB::table('post_tag');
foreach ($tags as $tag) {
$posts = $posts->orwhere('tag_id', $tag->id);
}
dd($posts->get());
This dumps an array of all posts that have any of the ID's, but I need to get an array of post_ids where it contains all tag_ids.
Thanks in advance!
It would be a good idea to use whereHas() on the Post model to eager load the tags and get only the Posts which have at least one of those tags.
$posts = Post::whereHas('tags', function($q) use ($tags)
{
$q->whereIn('id', $tags);
})->get();
Here, $tags would just be an array of tag id's. $posts would be a Collection of Posts. To get the array of id's from it, you can simply do this...
$ids = $posts->lists('id');
Or instead of calling get() originally, use ...->lists('id')
Edit
If you are looking for only those Posts which contain all of the tags, you want to pass some additional parameters to the whereHas function.
$posts = Post::whereHas('tags', function($q) use ($tags)
{
$q->whereIn('id', $tags);
}, '=', count($tags))->get();
What will happen is it will only grab posts that have a number of tags attached equal to the count of the tags in the tags array.
If you use this approach, be sure your pivot table is managed correctly in that it is not possible to attach a certain tag to a certain model more than once.

Categories