Combining query results in laravel - php

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.

Related

A correct query to get data from 3 tables and show it as a single element in laravel?

Suppose I have 3 tables, posts, post_images, and post_links.
post.id is a foreign key in both post_images and post_links.
Each post have multiple images.
I need a data which contains post, its images and its links as single element/array item. If there are 3 posts, I need 3 arrays with each array containing the posts images and links.
My code so far,
$data = DB::table('posts')
->join('post_images','posts.id' ,'=', 'post_images.post_id')
->join('post_links','posts.id' ,'=', 'post_links.post_id')
->select('posts.*')
->get();
with the above query I am getting all the records joined, If i have 3 records with 3 images each, I am getting 9 records, I just need 3 posts with its data as its sub arrays.
Any suggestion?
Here is the PostImage model
class PostImage extends Model
{
public function post() {
return $this->belongsTo(Post::class);
}
}
Here is the PostLink model
class PostLink extends Model
{
public function post() {
return $this->belongsTo(Post::class);
}
}
Here is the Post model
class Post extends Model
{
public function links() {
return $this->hasMany(PostLink::class);
}
public function images() {
return $this->hasMany(PostImage::class);
}
}
In the view you can reach everything you need.
#foreach ($posts as $post)
{$post->title} <br>
#foreach ($post->links as $link)
{$link->url} <br>
#endforeach
#foreach ($post->images as $image)
{$image->src} <br>
#endforeach
#endforeach
And if you want use less queries you could use eager loading to fetch all this data the first time. Eager Loading Laravel
Should look something like this
$posts = Post::with('images','links')->get();
if you already have relation in model you just have to use with method like
$data = PostModel::with('post_images','post_links')->get();
make it dd($data) and look at this. hope it will work.
References: https://laravel.com/docs/5.4/eloquent-relationships#eager-loading

Getting all item from choosen tag with Laravel

I have one page where I show image + tags added to image. I want to make tags clickable and when I click on some tag to open all images which has this tag too.
I have 3 tables: images for images, tags for tags and image_tag for tags assigned to images with columns (image_id and tag_id). My models are
In Tag model I've added this relation
public function byTags() {
return $this->belongsToMany('App\Image', 'item_tag');
}
I've added also this to my Image model but I'm not sure if I need it
public function byTags() {
return $this->belongsToMany('App\Tag');
}
This is the href link which should load all images
{!! $tag->tag !!}
This is my route
Route::get('byTag/{tag_id}', 'ImageController#byTag')->name('bytag');
byTag() function in the ImageController
public function byTag($tag_id){
$images = Tag::with('byTags')->whereId($tag_id)->get();
return view('bytag', compact('images'));
}
What is happen when I click on the button is that I get the tag on the view bytag instead of the images with this tag.
What I miss here?
You should load images using Image model and whereHas() method:
$images = Image::whereHas('tags', function ($q) use($tag_id) {
$q->where('id', $tag_id);
})->get();

Where condition on laravel model relationship

I have two models. i.e, Posts and Tags.
Post Model
public function tags()
{
return $this->belongsToMany('App\Models\Tag', 'tbl_post_tags', 'in_post_id', 'in_tag_id');
}
Tag Model
public function posts()
{
return $this->belongsToMany('App\Models\Post', 'tbl_post_tags', 'in_tag_id', 'in_post_id');
}
I want to fetch those posts which have php tag.
This is what I have done till now.
Search Controller
Post::with(['tags'])->skip(0)->take(5)->get();
I'm getting first five posts from table. But there is a post in these list which doesn't have PHP tag. So as per my requirement, I must get next post from table which have PHP tag.
I can't find any document on official site of laravel. If anyone knows the answer, it will be appreciated.
Assuming that you search the tags by name, here is the code.
Post::with(['tags'])->whereHas('tags', function($query){
$query->where("name", 'PHP');
})->skip(0)->take(5)->get();
Post::whereHas(['tags' => function($q) use($name){
$q->where('name', $name); // $name = 'PHP'
}])->take(5)->get();

Laravel - Eager load many-to-many, get one record only (not a collection)

My posts have images (many-to many since images can have other relations as well). In my pivot table I have a boolean field called 'featured' which designates the main image for that post. I want to display in the posts index page all the posts associated with the current user. I only want to get one image from the DB and that should be the featured image. Currently I can only get the featured images as a collection. The reason for this is if the user has lots of posts I don't want to go ahead and retrieve the featured image for all their posts (N+1) but rather using eager loading get the featured imaged with only 2 queries.
\\Post Model
public function images() {
return $this->belongsToMany(Image::class);
}
public function image(){
return $this->images()->where('featured', '=', true)->first();
}
public function featured_image(){
return $this->images()->where('featured', '=', true);
}
\\Controller
$user = Auth::user();
$posts = $user->posts()->with('image')->get();
// throws error
//Call to undefined method Illuminate\Database\Query\Builder::addEagerConstraints()
// if I do this
$posts = $user->posts()->with('featured_image')->get();
// I get all the user's posts, I get the featured image but as a collection even if I only have one record there
How can I do this?
I think this is probably the solution you want:
\\Post Model
public function images() {
return $this->belongsToMany(Image::class);
}
public function getFeaturedImageAttribute() {
return $this->images->where('featured', true)->first();
}
\\Controller
$user = Auth::user();
$posts = $user->posts()->with('images')->get();
In the resulting collection of posts, each post will have a 'featured_image' attribute that can be accessed like this:
foreach ( $posts as $post )
{
$featured_image = $post->featured_image;
// do something with the image...
}
IMPORTANT: Because the accessor method uses '$this->images' instead of '$this->images()', it will run using the eager loaded 'images' Collection's where() and first() methods instead of the query builder. This results in a decent chunk of PHP processing but no new queries.
If you are limited to using only two queries, you can use the following code to achieve your goal:
$posts = $user->posts;
$idOfPosts = $posts->pluck('id');
$featuredImages = Image::whereIn('post_id', $idOfPosts)->where('featured', true)->get();
enter code here
While this solution is not an Eager Loading approach, it does resolve the N+1 query problem.

Ordering Related Models with Laravel/Eloquent

Is it possible to use an orderBy for an object's related models? That is, let's say I have a Blog Post model with a hasMany("Comments"); I can fetch a collection with
$posts = BlogPost::all();
And then run through each post, and display the comment's last edited date for each one
foreach($posts as $post)
{
foreach($post->comments as $comment)
{
echo $comment->edited_date,"\n";
}
}
Is there a way for me to set the order the comments are returned in?
This is the correct way:
BlogPost::with(['comments' => function ($q) {
$q->orderBy('whatever');
}])->get();
The returned object from the relationship is an Eloquent instance that supports the functions of the query builder, so you can call query builder methods on it.
foreach ($posts as $post) {
foreach ($post->comments()->orderBy('edited_date')->get() as $comment) {
echo $comment->edited_date,"\n";
}
}
Also, keep in mind when you foreach() all posts like this, that Laravel has to run a query to select the comments for the posts in each iteration, so eager loading the comments like you see in Jarek Tkaczyk's answer is recommended.
You can also create an independent function for the ordered comments like you see in this question.
public function comments() {
return $this->hasMany('Comment')->orderBy('comments.edited_date');
}
And then you can loop them like you did in your original code.
Create a JOIN and select just the column you want to order on:
$post = BlogPost::join('comments', function($j) {
$j->on('posts.id', '=', 'comments.post_id');
$j->select('comment_date_or_other_col');
})
->orderBy('comment_date_or_other_col', 'DESC')->first();
Yes:
$posts = BlogPost::with('comments')
->orderBy('comments_table_name.column_name')
->get();
And you can also set that in your relation:
public comments()
{
$this->hasMany("Comments")->orderBy('comments.column_name');
}

Categories