I have a problem, I have 2 tables (posts, comments) and I want to select all posts + the number of their comments.
I could create a column "comments" in posts and write the counter in there, but I think that's a bad solution, so I am looking for something like virtual fields/columns in mysql to get the number of comments in the posts query.
Can I do this in Laravel?
In your Comment Model:
public function post(){
return $this->belongsTo('Post', 'post_id');
}
In your Post Model:
public function comments(){
return $this->hasMany('Comment', 'post_id');
}
In your controller, get a post with all its comments.
$post = Post::with('comments')->find(1);
In your view, display the post and the number of comments it has. Assuming your post has a title field.
Post Title: {{ $post->title }}
The post has {{ $post->comments->count() }} comments
Related
Route:
Route::get('/posts/{post}', [PostController::class, 'show']);
Controller:
public function show(Post $post){
$postData = $post->load(['author' => function($query){
$query->select('post_id', 'name');
}])
->get(['title', 'desc', 'created_date'])
->toArray();
}
This returns all the posts in the database, While I only want to get the selected post passed to the show function.
So if I visit /posts/3, It should show data related to the post with id = 3, not all posts.
The result should be an array with only the selected post.
When you are inside the Controller you already have the Post you want. If you call get you are getting all posts with a new query.
To select specific columns from a relationship, you can do it like this:
public function show(Post $post)
{
$post->load('author:id,name,age'); // load columns id, name, age from author
}
Notice that including the id is required.
To specify columns for the Post, there's no way to do it per route, the only way is to override how Laravel resolves the implicit binding. For that, you can add this in your Post model class:
public function resolveRouteBinding($value, $field = null)
{
return $this->whereKey($value)->select(['id', 'body', 'author_id'])->firstOrFail();
}
Notice that including the author_id is required to load the Author afterwards inside the Controller method. Also keep in mind this will affect all routes where you implicitly load a Post.
Please don't use get() function on the model.
public function show(Post $post)
{
$post->load(['author' => function ($query) {
$query->select(['post_id', 'name']);
]);
dd($post->toArray());
}
Btw, the author shouldn't have a post_id, because author can have multiple posts. you should update the database structure or you are doing wrong in the controller. (my bad, Thanks #techno)
I have posted a related question a few hours ago but I can not understand why Laravel eloquent is so complicated... once again I read many posts on the subject and not one gave me the solution to a very simple request.
Here is the simple example where a post belongs to one article and one user only. So the relations are defined as below in the Post model:
public function article()
{
return $this->belongsTo(Article::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
I am quite new to Laravel eloquent... so I simply want to display a list of all the posts with their id, comment and related user name and article title as per relation defined in the model above. I believe that is quite simple and I just learned the "with" today.
So in the PostController, I have:
$posts = Post::orderBy($sortField,$sortOrder)
->with(['article','user'])
->get();
This gives me a collection with all the posts.
First question, do I have to manipulate the 2 related fields in such a way that they become easily accessible in the list view below? Or can I avoid this in controller in anyway by putting it in the query?
foreach ($posts as $post) {
$post->article = $post->article()->pluck('article');
$post->user = $post->user()->pluck('name');
}
In the above, the field $post->article lists all the article titles in an array (article field).
If I add [0] at the end, I get the first element of the array.
If I use [post->article_id] I get the wrong related title.
So nothing works the way I want... why is it so complicated???
Ultimately, here is what I need to do in the list view.
#foreach ($posts as $post)
<p>{{ $post->id }} : {{ $post->comment }} by {{ $post->name }} from article {{ $post->article }}</p>
#endforeach
By doing the query in query builder instead, I get what I want immediately... so how to do the same in eloquent?
$users = DB::table('posts')
->join('articles', 'articles.id', '=', 'posts.user_id')
->join('users', 'users.id', '=', 'posts.user_id')
->select('users.*', 'articles.article', 'users.name')
->get();
It is not clear what is making you misunderstand it, so here is an explanation.
First of all, you have two belongsTo relations user & article in your post model.
When you run this
$posts = Post::orderBy($sortField,$sortOrder)
->with(['article','user'])
->get();
You get a collection of Posts and with the article and the user in the attributes $post->article & $post->user respectively. That queryBuild will launch 3 queries, one for the posts, one for the articles and one final for the users.
Now, in your blade, you want to access the attribute article in your Article model wich is inside the Post model so you do it like this:
#foreach ($posts as $post)
.... from article {{ $post->article->article }}</p>
#endforeach
Notes: When you call $post->article() you get a query builder, so you follow it with a
first() => instance of a model / null
get() => Collection of instance of a model or empty collection
pluck() => array of the attribute plucked
value() => the value of the first element as if you used pluck
... (there are other ways to get results)
When you call $post->article without parenthesis, it will run :
first() if the relation is a belongsTo
get() if the relation is belongsToMany or HasMany or....
OR
It will give you the attribute if already fetched (either a collection or a model instance).
So, since you used with('article') in the first query (already fetched), $post->article will return an instance of the model Article and no new query will be run.
Let's say I have a post with many comments and I properly defined $post->comments relation in my post model. Comments have a column named confirmed with the value of 0 or 1. How can I select confirmed (rows which have confirmed value of 1) rows in my blade template?
This can help you
In your Post model
public function comments()
{
return $this->hasMany(Comment:class);
}
public function confirmedComments()
{
return $this->comments()->whereConfirmed(1);
}
And from your controller
$comments = $post->confirmedComments;
If in blade, you want select confirmed comments, it's so easy to do it with
#if($comment->confirmed)
//confirmed comment
#else
//
#endif
Hope it'll be helpful !
There are many ways to do this.
If you already have the comments eager loaded, then you can use the where() method on the comments collection:
$confirmedComments = $post->comments->where('confirmed', 1);
This will go through all the comments in the collection and only return those that are confirmed.
If you don't have the existing collection, you can add the where clause to the relationship query. This way, you're only getting confirmed comments from the database, and you're not wasting resources retrieving and building models for comments you're not using
$confirmedComments = $post->comments()->where('confirmed', 1)->get();
And, another option, would be to create a new relationship for just confirmed comments, and that way you can eager load the new relationship:
public function comments()
{
return $this->hasMany(Comments::class);
}
public function confirmedComments()
{
return $this->comments()->where('confirmed', 1);
}
$confirmedComments = $post->confirmedComments;
I have two models
User.php
public function opportunities()
{
return $this->hasMany('App\Opportunity');
}
Opportunity.php
public function user()
{
return $this->belongsTo('App\User');
}
I have user_id column in opportunities table and inserted user id (from user table using Auth) every time user posts record.
Now i need a view to return "this post is posted this user".
First I find the post id by
$posts = Opportunity::find($id);
$posted_by = User::find($posts->user_id);
return view('opportunity.detail')->with('post', $posts, 'posted_by', $posted_by);
I have rendered user name by {{$posted_by->name}}
But I got undefined constant in the view file $posted_by while $post is fine. Am I doing it in right way or not? I am passing two array variable to the post and its not working. Any help will be appreciated.
Your controller could look like:
return view('opportunity.detail', [
'post' => Opportunity::find($id);
]);
In the view to show user name:
Post {{ $post->title }} posted by {{ $post->user->name }}
https://laravel.com/docs/5.5/eloquent-relationships#relationship-methods-vs-dynamic-properties
If for some reason you want to use ->with(), do this:
->with(['post' => $posts, 'posted_by' => $posted_by]);
Or:
->with('post', $posts)->with('posted_by', $posted_by);
You can put this
public function getUserName() {
return User::where('id', $this->user_id)->first()->name;
}
in your Opportunity.php model and call it in you view
#foreach ($posts as $post)
{{ $post->getUserName() }}
#endforeach
I have created a pivot table post_tag and inserting data into it using sync method.
Now I want to fetch respective tags of a post to show in the view like following
{{ $post->tags }}
How can I do this? Thanks in advance.
You have to convert your object to array. In laravel, you can use like this.
{{ $post->tags->toArray() }}
For more detail in documentation.
Edit
In your Post model.
public function tags()
{
return $this->hasMany('Tag'); // One to Many
return $this->belongsToMany('Tag'); // Many to many
}
You can get like this.
$tags = Post::find($postId)->tags;
Or
$post = Post::find($postId);
$tags = $post->tags;
Hope it will be work for you.