I have a post model, a user model and a comment model.
My users can post posts and also can comment to posts.
I want to get the comments of posts with posts but, my users can also block users too.
I mean, if the active user has some other blocked users, and if the comments of post has comments created by these users, I have to exclude them.
And here comes the problem.
$posts = $user()->posts()->get();
$blockedUserIdentifiers = (array) $viewer->getBlockedUserIdentifiers();
$posts->with(array('comments' => function($query) use ($blockedUserIdentifiers)
{
if( ! empty($blockedUserIdentifiers))
$query->whereNotIn('user_id', $blockedUserIdentifiers);
}))->with('user'); // ? Ups?
I want to use relationships of posts and also comments, because the work is not finished here. But if I use foreach on posts array, it will be very strange to continue and; if I use foreach on posts object, I is hard for me to continue.
Because i have blockedUser condition, I can not eager load too.
What is the best practice of my case or, how should I add $comments result object to $posts object like laravel does?
Or how can I continue to add relationship results to that result?
Putting constraints on multiple relationships with with().
$posts->with(array('comments' => function($query) use ($blockedUserIdentifiers)
{
// if( ! empty($blockedUserIdentifiers))
// $query->whereNotIn('user_id', $blockedUserIdentifiers);
}, 'comments.user', function($query) use ($blockedUserIdentifiers)
{
// Constraints on the users can go here
if( ! empty($blockedUserIdentifiers))
$query->whereNotIn('id', $blockedUserIdentifiers);
}))->get();
Or doing it while echoing.
foreach($posts as $post) {
// If you want to keep your relationships clean, you can just not echo the post instead
if(array_search($post->user_id, $blockedUserIdentifiers) !== false) {
continue;
}
echo $post->content;
foreach($post->comments as $comment) {
echo $comment->user;
echo $comment->content;
}
}
Related
I have a User model that does have many Networks. Each Network does have many Lists.
I have this code in the User model that is really slow:
foreach ($this->networks as $network) {
if ($myCondition) {
foreach ($network->lists()->get()->lists('id')->toArray() as $newId) {
$ids[] = $newId;
}
}
}
I wonder if there is a way to load all lists of all networks before the 2 foreach loops.
This may speed it up, although I'm not certain:
$this->networks()->with('lists')->get()->pluck('lists')->flatten()->pluck('id')
Also if it isn't a many to many relationship you could consider putting user_id as a column in your 'lists' table if speed is really important
This is lazy eager loading:
$this->networks->load('lists');
That loaded the lists relationship for all the networks.
If you just want this list of ids and don't need the actual records returned you can do that as well:
$ids = Lists::whereHas('network', function ($query) {
$query->whereIn('id', $this->networks->pluck('id'));
})->pluck('id');
If you want because Lists belongsTo Network you can go around the relationship:
$ids = Lists::whereIn('network_id', $this->networks->pluck('id'))
->pluck('id');
Just keep code in User model. In controller, you can do:
$User->load('networks', function($q){
$q->with('lists');
});
There in:
'networks' and 'lists' is your relationship name.
Further:
You can lazy load all your relation ship, in child by child.
$User->load('networks', function($q){
$q->with('lists', function($q1){
$q1->with('child_relationship_of_lists');
});
});
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.
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.
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');
}
I am using Laravel's Eloquent ORM and I'm having trouble eager loading items for display.
Here is the scenario:
Users follow Blogs
Blogs have Posts
I have a database table named Relationships, this table is used to store the User ID and the Blog ID to show which User is following which Blog. I have a table for Blogs describing the Blog and I have a table for Posts. The Relationships table would be my pivot table to connect the Users with the Blogs tables together. Now, I need to list out all the posts from all the Blogs the User follows in a list.
Here is my User model:
public function following() {
return $this->has_many_and_belongs_to('Blog', 'relationships', 'user_id', 'blog_id');
}
Here is my Blog model:
public function followers() {
return $this->has_many_and_belongs_to('User', 'relationships', 'blog_id', 'user_id');
}
public function posts() {
return $this->has_many('Post');
}
This is how I am trying to retrieve the posts in a list:
$posts = User::with(array('following', 'following.posts'))
->find($user->id)
->following()
->take($count)
->get();
This code only lists out the actual Blogs, I need their Posts.
Thank you for your help, please let me know if you need any more details.
SOLUTION:
I slightly modified the accepted answer below, I decided to use the JOIN to reduce the amount of SQL calls to simply 1 call. Here it is:
$posts = Post::join('blogs', 'posts.blog_id', '=', 'blogs.id')
->join('relationships', 'blogs.id', '=', 'relationships.blog_id')
->select('posts.*')
->where('relationships.user_id', '=', $user->id)
->order_by('posts.id', 'desc')
->take($count)
->get();
This is not achievable by native Eloquent methods. But you can use a bit of Fluent methods to join those tables. For instance:
Edit here: I've added the eager loading to Post query.
$user = User::find(1);
$posts = Post::with('blog') // Eager loads the blog this post belongs to
->join('blogs', 'blogs.id', '=', 'posts.blog_id')
->join('relationships', 'relationships.blog_id', '=', 'blogs.id')
->where('relationships.user_id', '=', $user->id)
->order_by('posts.id', 'desc') // Latest post first.
->limit(10) // Gets last 10 posts
->get('posts.*');
foreach ($posts as $post) {
print($post->title);
}
If you also need a list of all blogs that such user is following to show on a sidebar, for instance. You can DYI instead of relying on Eloquent, which should be faster and more customizable. For instance:
$user = User::with('following')->find(1);
// This creates a dictionary for faster performance further ahead
$dictionary = array();
foreach ($user->following as $blog) {
$dictionary[$blog->id] = $blog;
}
// Retrieves latest 10 posts from these blogs that he follows
// Obs: Notice the array_keys here
$posts = Post::where_in('blog_id', array_keys($blog_ids))
->order_by('posts.id', 'desc')
->limit(10)
->get();
// Hydrates all posts with their owning blogs.
// This avoids loading the blogs twice and has no effect
// on database records. It's just a helper for views.
foreach ($posts as $post) {
$post->relationships['blog'] = $dictionary[$post->blog_id];
}
On view:
foreach ($user->following as $blog) {
print($blog->title);
}
foreach ($posts as $post) {
print($post->title . ' #'. $post->blog->title);
}