I have a One to Many relationship set up for my "announcements" having many "comments".
Currently, when my user loads up the app page, I have it send the 30 most recent announcements like such:
Route::get('/app', function () {
$posts = Announcement::take(30)->orderBy('id', 'desc')->get();
return View::make('app')->with([
//posts
'posts' => $posts,
//orders
'orders' => $orders
]);
}
When I echo out the announcements in blade using a foreach loop through the $posts object, I want to also echo out the comments for each post under the respective post.
Is it possible to pass the comments for a post as part of the actual post object? For example, it would be nice if I could do this:
#foreach ($posts as $post)
//echo out the post
{{$post->content}}
//echo out the comments relating to this post
{{$post->comments}}
#endforeach
#Amr Aly gave you the right answer, and I would like to add on top of it.
When you loop through your comments as he showed you (and you should), it will make a different query for each comment. SO if you have 50 comments, that's 50 more queries.
You can mitigate that by using eager loading
$posts = Announcement::with('comments')
->take(30)->orderBy('id', 'desc')
->get();
Then just loop the way he showed you. THis will limit the queries to 2 only. You can read more from the docs here: https://laravel.com/docs/5.4/eloquent-relationships#eager-loading
You can add another foreach for your comment like this:
#foreach ($posts as $post)
//echo out the post
#if($post->comments->count())
#foreach ($post->comments as $comment)
// {{ $comment }}
#endforeach
#endif
#endforeach
Related
I have the following query that will bring all Auth user friends :
$usrusrmembs = DB::table('usrusrs')
->where('accepted', 1)
->where('user_id', $u_id)
->orwhere('friend_id', $u_id)
->pluck('friend_id', 'user_id');
Next is a foreach to loop the ids received from the above query, get the posts of all Ids, and then sending the result to a blade :
foreach($usrusrmembs as $key => $val){
$rec_users = ($key == $u_id) ? $val : $key;
$f_usrs_psts = DB::table('posts')
->where('posts.user_id', $rec_users)
->get();
}
return view('updates', ['f_posts' => $f_usrs_psts] );
The output in the blade shows only posts of one friend , while ignores the others. I feel there is a problem in the posts query, where it only sends to the blade the last processed ID. If this is the problem, then how can I solve it ?
This is where Laravel really shines. Take advantage of the relationships and do this all in one query with eager loading. I don't know what your model relations are, but something like this:
$usrusrmembs = \App\UserUserModel::where('accepted', 1)
->where('user_id', $u_id)
->orwhere('friend_id', $u_id)
->with('posts')
->get();
If you want more control, you can use a combination of closures and whereHas, but this above should get you close. Then, in your view you can loop on the posts for each:
#foreach($usrusrmembs as $usr)
echo $usr-name // etc
#foreach ($usr->posts as $post)
echo $post->whatever
#endforeach
#endforeach
This is not going to give you exactly what you need, but the idea should help you to work through it and you can skip the whole n+1 issue by removing the foreach loop in your controller.
Just an example:
let's say I have Post model, and the Comment model. Post, of course, have Comments, one-to-many relation.
I have to display list of posts with comments below it.
I'll get my posts in the controller:
$posts = Post::get(), I'll pass it to the blade view and then I'll loop through it
#foreach($posts as $post)
{{ $post->title }}
{{ $post->comments }}
#endforeach
where $post->comments is some relation
public function comments()
{
return $this->hasMany(Comment::class);
}
As we know, that query will be executed many times.
Now my question: how we should optimize it?
Return Cache::remember in the getter?
Get (somehow?) those comments, when getting the posts in one query? Something like join query? I know that I can write that kind of query, but I'm talking about Eloquent's query builder. And then how get the comments within the loop? Wouldn't {{ $post->comments }} call the relation again instead of getting stored data?
Different solution?
You can do $posts = Post::with('comments')->get() to eager load the comments with the post. Read more about it in the documentation: https://laravel.com/docs/5.7/eloquent-relationships#eager-loading
Also, to display the comments you would want to add another foreach loop. It would look something like this:
#foreach($posts as $post)
{{ $post->title }}
#foreach($post->comments as $comment)
{{ $comment->title }}
#endforeach
#endforeach
You’ve probably cached some model data in the controller before, but I am going to show you a Laravel model caching technique that’s a little more granular using Active Record models
Note that we could also use the Cache::rememberForever() method and rely on our caching mechanism’s garbage collection to remove stale keys. I’ve set a timer so that the cache will be hit most of the time, with a fresh cache every fifteen minutes.
The cacheKey() method needs to make the model unique, and invalidate the cache when the model is updated. Here’s my cacheKey implementation:
public function cacheKey()
{
return sprintf(
"%s/%s-%s",
$this->getTable(),
$this->getKey(),
$this->updated_at->timestamp
);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
public function getCachedCommentsCountAttribute()
{
return Cache::remember($this->cacheKey() . ':comments_count', 15, function () {
return $this->comments->count();
});
}
yes u can do like that in controller
$minutes = 60;
$posts = Cache::remember('posts', $minutes, function () {
return Post::with('comments')->get()
});
in blade u can get like that
#foreach($posts as $post)
{{ $post->title }}
#foreach($post->comments as $comment)
{{ $comment->title }}
#endforeach
#endforeach
for more information read this article
I have a table posts and posts_contents.
And I want to get a content from one post only if that post has display = 1.
(I need two separate tables because of language support)
posts:
id user_id display
1 2 0
2 2 1
3 2 0
4 2 1
posts_contents
id post_id lang_id name description
1 1 1 Hello World
2 2 1 Here Is What I wanna show!
3 3 1 Don't Show the others
4 4 1 Hey Display that one too
So in laravel I use eloquent relationships, but I just don't understand how to use it in that particular case. In the documentation I found only cases such as:
$p = App\Posts::find(1)->contents;
Which works great, however what I want is something like this:
$p = App\Posts::where('display',1)->contents;
But it doesn't work... So question is: what is the right way to do so?
Any help is appreciated, Thanks!
Update
I need to get multiple posts at once, not just one.
You want to use find() method like this:
$post = App\Posts::where('display', 1)->find($postId)->contents;
Then in a view for one-to-one relationship:
{{ $post->description }}
For one-to-many:
#foreach ($post->contents as $content)
{{ $content->description }}
#endforeach
If you want to load multiple posts with contents only for one language, use filtering by a language. Use with() to eager load contents:
$posts = App\Posts::where('display', 1)
->with(['contents' => function($q) use($langId) {
$q->where('lang_id', $langId);
}])
->get();
Then in a view for one-to-one:
#foreach ($posts as $post)
{{ $post->contents->description }}
#endforeach
For one-to-many:
#foreach ($posts as $post)
#foreach ($post->contents as $content)
{{ $content->description }}
#endforeach
#endforeach
You can read about the difference between find() and get() methods here.
App\Posts::where will return a collection. So if you only want 1 result you should use App\Posts::where('display',1)->first()->contents
You need to call the first method before you call any relationship:
$p = App\Posts::where('display', 1)->first()->contents;
Or, if you want to fetch a collection of posts, you can:
$posts = App\Posts::where('display', 1)->get();
$posts->each(function ($post) {
$post->contents;
});
Otherwise, you will just have a Query Builder object, without the actual results you want.
I have 3 tables :post , tag , tag_post.
I save post_id in post / tag_id in tag / and both of them in tag_post.
how can I show every post's tags ? how can I select data from tag_post table?
it's my Post model :
public function tag()
{
return $this->belongsToMany('Tag','tag_post');
}
and it's my Tag model :
public function post()
{
return $this->belongsToMany('Post','tag_post');
}
and it's my controller :
$posts=Post::orderBy('id','DESC')->paginate(5);
///but I dont know how can i show each post's tags under it
thanks for your time.
A few things here (I will keep it simple so no orderBy or anything else, also I assume you rename relations to plural: tags() and posts() to make it easier to read and work with):
$posts = Post::paginate(5); // returns a Collection of Post models, 1 db query
foreach ($posts as $post) {
$post->tags; // Collection of Tag models, fetched from db for each $post
}
This means 5+1 queries. It doesn't scale at all of course, so we need http://laravel.com/docs/eloquent#eager-loading
Which leads us to:
$posts = Post::with('tags')->paginate(5); // returns a Collection of Post models
// runs 1 query for posts and 1 query for all the tags
foreach ($posts as $post) {
$post->tags; // Collection of Tag models, no more db queries
}
So to list all the tags you could do this:
#foreach ($posts as $post)
<tr>
<td>{{ $post->title }}</td>
<td>
#foreach ($post->tags as $tag)
{{ $tag->name }} // or whatever it is that you want to print of the tag
#endforeach
</td>
</tr>
#endforeach
If you need to get the tags from each post you need a foreach loop.
foreach ($posts as $post)
{
var_dump($post->tags); // your individual post's tags will be here
}
Also, as much as I don't like to poke my nose around, it would be better if you follow the conventions in the framework itself. (i.e. use plurals form in many-to-many relationship)
Post Model
public function tags() // <-- note the plurals
{
$this->belongsToMany('Tag', 'tag_post');
}
Tag Model
public function posts() // <-- note the plurals
{
$this->belongsToMany('Post', 'tag_post');
}
If you need to get data from the tag_post table, check out the documentation on working with pivot table.
http://laravel.com/docs/eloquent#working-with-pivot-tables
I have got two tables , I would use facebook POST as an example.
Post Table
Comment Table
My query
$result = DB::table('posts')
->join('comments', 'posts.post_ID', '=', 'comments.post_ID')
->get();
I will receive an array of posts and comments merge. For each comments that exist , they will have the posts data.
What i want is to be able to do something like
foreach($posts as $post){
foreach($post['comment']{
}
}
Any idea how i can do that?
Something like this should work:
$result = DB::table('posts')
->join('comments', 'posts.id', '=', 'comments.post_id')
->get();
In the view:
#foreach($posts as $post)
{{ $post->post_title }}
{{ $post->message }}
#endforeach
Make sure that, field names in ->join('comments', 'posts.id', '=', 'comments.post_id') are right or change accordingly. post_title/comment_text is used to demonstrate the example, change to it's original table's field name and {{ }} is used in the example to echo the data, if you are not using Blade then use echo $post->post_title instead.
Update::
If you use Eloquent then use:
// Assumed you have defined comments as relationship method
$posts = Post::with('comments')->get(); // it's faster and called eager loading
Then in the view:
#foreach($posts as $post)
{{ $post->post_title }}
#foreach($post->comments as $comment)
{{ $comment->message }}
#endforeach
#endforeach
I will receive an array of posts and comments merge. For each comments that exist , they will have the posts data.
This is correct SQL behavoir when using a join. You will get the contents of both the rows inside the posts and comments rows on your JOIN.
According to Laravel 4.2's documentation on the join method, the parameters of the join function are:
join($table, $one, $operator = null, $two = null, $type = 'inner', $where = false)
Using an INNER JOIN, you are only going to get rows returned to you with your query (using an inner join) if you have a comment for all of the posts that you want data from. Additionally, with your INNER JOIN, if there is no comment on your post, you will not get any posts returned to you.
Also, you are not going to be able to separate all of your comments from your posts, which may mean that you are getting results returned for posts that you
The simple way to solve this for you would be to make two eloquent models:
class Post extends Eloquent {
protected $table = 'posts';
public function getComments() {
return $this->hasMany('Comments', 'post_id');
}
}
class Comments extends Eloquent {
protected $table = 'comments';
}
From that you can query for all of the posts with eager loading:
$posts = Post::with('comments')->get();
and inside your view, you go:
foreach($posts as $post) {
// echo the $post? (title, author etc)
foreach($post->comments() as $comment) {
// echo the $comment? (author, content etc)
}
}