Laravel eloquent "with" the easy way - laravel

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.

Related

Detach on eloquent collection not working

In a Post model I have:
public function tags()
{
return $this->morphToMany(\App\Models\Tag::class, 'taggable');
}
But when I then do:
$posts = Post::all();
$posts->tags()->detach();
I get
Method Illuminate\Database\Eloquent\Collection::inputs does not exist.
Based on your code, it seems like you're trying to remove all the tags from all the existing posts in your application.
If this is what you're trying to do, then follow #A.A Noman comment: you should detach them by iterating the collection, one by one.
Another option is to just clear the intermediate table containing the relations.
If what you're trying to do here is detaching all the tags from a single Post, you can search the post and then detach all the tags:
$post = Post::find($id);
$post->tags()->detach();
UPDATE
To iterate the collection and remove all the Tags from all the Posts:
$posts = Post::all();
foreach ($posts as $post) {
$post->tags()->detach();
}
Use Laravel's with()
https://laravel.com/docs/8.x/eloquent-relationships
To bring the tags related to this collection
Example:
$posts = Post::with('tags');
$posts->tags()->detach();
Likewise, if you want to remove a particular entity relationship from the pivot table, you can use the detach method.
For example, if you want to remove a certain author from a book, you can do so.
$book->authors()->detach($authorId);
Or you can pass multiple IDs as an array.
$book->authors()->detach([4, 5, 8]);
Or, if you want to remove all authors from a book, use detach without passing any arguments.
$book->authors()->detach();

laravel passing data to view blade

//model
return $this->hasMany('App\Model\Order', 'customer_id')->select('name');
//controller
$customer = Model\customer::find($id);
return view('customer', array('data' => $customer));
//view (blade)
{{ $data }} //{"id":1,"name":"Tony"}
{{ $data->orders }} //[{"name":"T-shirt"},{"name":"Macbook"}]
i'm new in laravel
I have a question about passing data to view.blade
I used hasMany to join 2 table together and pass $data to view
when I try to output $data, I could not see order object inside of data object
but when I did $data->orders, it shows object
can anyone tell me how it works?
The behavior you are seeing is because Laravel lazy loads relations when they are accessed. If the relation has not been loaded, Laravel will send another query and add it to your $data object behind the scenes. That's why when you dump the $data variable, you are not seeing the orders.
To demonstrate, run the following snippet.
{{ $data }} //{"id":1,"name":"Tony"}
// Laravel will lazy load the orders relation
{{ $data->orders }} //[{"name":"T-shirt"},{"name":"Macbook"}]
// Now the $data object has the orders property.
{{ $data }} //{"id":1,"name":"Tony", "orders": [{"name":"T-shirt"},{"name":"Macbook"}]}
Solution
You have a couple options here. Here are 2.
1. Eager load relation when querying model.
$customer = Model\customer::with('orders')->find($id);
This is the preferred method as it prevents n+1 querying.
2. Load relation after model has been queried.
$customer = Model\customer::find($id);
$customer->load('orders');
Now when you dump the model, you will see the orders property.
In your model,
public function orders(){
return $this->hasMany('App\Model\Order', 'customer_id');
}
In view
{{ $data->orders->order_name }}
I hope it will works
You have defined hasMany relationship in your model. So, for a particular customer there can be multiple orders. So, it shows in array. You have to loop through:
foreach($data->orders as $order)
{
$order->name
}
to get the orders.

Paginate a relationship?

Im trying to find a user and paginate their posts, I've tried:
User::find($id)->posts()->paginate();
This paginates the posts, but I lose the users information. I also need to get the users information out along with their posts.
How can I do this?
This can be done by passing a function when eager loading:
User::find($id)->with(['posts' => function($query) use ($limit) {
$query->paginate($limit);
}])->get();
Just inverse it
$posts = Post::where('user_id',$user_id)->with('user')->paginate();
Access user
foreach ($posts as $post){
$post->user;
}
I have not tested this but I believe it could work something like this
$user = User::find($id)
Send that to the view and then you can access the User as
{{ $user->property }}
and for his posts
#foreach ($user->posts()->paginate(15) as $post)
// do something
#endforeach
{{ $user->posts()->links() }}
UPDATE
However this is not the best practice.
You could also send both $user and $user->posts()->paginate() to the view.

Laravel Eloquent complex join statement

My database structure is the following:
So I've got a Bloggers table (sorry for the typo in the image), which has 3 topic fields, all 3 are foreign keys to the topics' table id.
I made a Blogger view front-end with a simple table showing all blogger columns. At the moment, the id's of the topics are being shown instead of the names though.
How can I change this?
I've already tried the following in my Controller, but that just creates duplicates in the view with different values in the main_topic column.
$bloggers= DB::table('bloggers')
->join('topics', function ($join) {
$join
->on('bloggers.main_topic', '=', 'topics.id')
->orOn('bloggers.subtopic1', '=', 'topics.id')
->orOn('bloggers.subtopic2', '=', 'topics.id');
})
->select('bloggers.*', 'topics.name as main_topic')
->get();
return view('pages.bloggers', compact('bloggers'));
The answer to my specific question was a little different from the answers given, so I'll post it here in case anyone needs it in the future.
So I've got 3 tables: bloggers, topics and a pivot table called blogger_topic. bloggers contains 3 foreign keys (integers) to topics called main_topic, subtopic1, subtopic2. The pivot table contains a blogger_id, and a topic_id.
What I ended up with:
Blogger.php model:
public function mainTopic()
{
return $this->belongsTo('App\Models\Topic', 'main_topic', 'id');
}
public function subtopicOne()
{
return $this->belongsTo('App\Models\Topic', 'subtopic1', 'id');
}
public function subtopicTwo()
{
return $this->belongsTo('App\Models\Topic', 'subtopic2', 'id');
}
Topic.php model:
public function bloggers()
{
return $this->belongsToMany('App\Models\Blogger');
}
View (blogger.blade.php):
#foreach($bloggers as $blogger)
{{ $blogger->mainTopic }}
{{ $blogger->subtopicOne }}
{{ $blogger->subtopicTwo }}
#endforeach
I think you should change your database tables if possible as below (sample link), by creating a pivot table. And then I think you should make relationships in Blogger and Topic model files with many-to-many. Then it would be very easy to fetch all related topics. For eg,
$topics = $blogger->topics;
Here you can reference the sample db table designs
I kindly recommend you to use models in Laravel coz they'll make your life more simple. Hope this help.
If you cant change your database, you could use group_concat on the results. Not sure if it will work but something like:
DB::table('bloggers')
->join('topics', function ($join) {
$join
->on('bloggers.main_topic', '=', 'topics.id')
->orOn('bloggers.subtopic1', '=', 'topics.id')
->orOn('bloggers.subtopic2', '=', 'topics.id');
})
->groupby('bloggers.id')
->select('bloggers.*', 'topics.name as main_topic', DB::raw('group_concat(topics.name)'))
->get();

get posts by self and posts by all follows with attached user using with() or joins and order by post created_at

I have a follow system setup from this tutorial.
Creating the Twitter following model in Laravel 4
It works for getting follows and followers and for saving them. But I want to list all of my posts and all posts of the people I follow, along with the related user object for each one and order them all by the posts created_at column.
Rather than try to pick some code to show what I have tried, lets just say I have spent two days trying every combination of join(), leftJoin(), nested joins, where(), orWhere(), nested wheres, with(), joins and wheres nested in with() that I can think of and I just can't figure it out.
For the follows I have a pivot table with user_id and follow_id. Here are the relationships in my User model.
/**
* User following relationship
*/
public function follows()
{
return $this->belongsToMany('User', 'user_follows', 'user_id', 'follow_id')
->withTimestamps();
}
/**
* User followers relationship
*/
public function followers()
{
return $this->belongsToMany('User', 'user_follows', 'follow_id', 'user_id');
}
Twit.php model. (Actually my posts are called twits but same concept)
class Twit extends Eloquent {
protected $fillable = ['twit', 'user_id'];
public function user()
{
return $this->belongsTo('User');
}
}
User.php model
class Twit extends Eloquent {
protected $fillable = ['twit', 'user_id'];
public function user()
{
return $this->belongsTo('User');
}
}
I've tried talking myself through this but none of the eloquent functions seem to do what I think they should do. To be clear, here is verbally what I need to happen.
Get each twit with its user and order by twits.created_at
only where user.id = Auth::user()->id
or where user.id is in Auth::user()->follows
Help writing this out as a raw query would work too.
Thanks for any help.
UPDATE: Deleted my own answer to save others from getting confused by it since it was way off and wasn't working 100%.
The selected answer works perfectly. Here is the selected answer by #philipbrown with added eager loading for the user and ordered by the twit created_at date
$twits = Twit::whereIn('user_id', function($query)
{
$query->select('follow_id')
->from('user_follows')
->where('user_id', Auth::user()->id);
})->orWhere('user_id', Auth::user()->id)
->with('user')
->orderBy('created_at', 'DESC')
->get();
And in the view
#foreach($twits as $twit)
<li>
<div class="twit-gravitar">
<img src="{{ getGravitar($twit->user->gravitar) }}">
</div>
<div class="twit">
<div class="twit-handle">
{{link_to('/twits/'.$twit->user->username, '#'.$twit->user->username) }}
</div>
<div class="twit-text">{{ $twit->twit }}</div>
</div>
</li>
<hr class="twit-separator">
#endforeach
I'll walk through step-by-step how I would solve this problem. I find it easier to get my head around the raw query before I convert that into ORM methods, so I'll write this out as I would work through it, rather than just give you the answer.
Writing the raw query
So first I would simply get all twits (I'm guessing it's twits?):
SELECT * from twits
Next I would refine this by only selecting the from the current user (using user_id 1 as an example):
SELECT * FROM twits WHERE user_id = 1
Next we can use an SQL subselect to find all the users that the current user follows:
SELECT * FROM twits WHERE user_id IN (SELECT follow_id FROM user_follows WHERE user_id = 1) OR user_id = 1
Now if you run that on your database and change the user_id you should get a stream of twits that you were expecting.
Converting to Eloquent
Now that we have the raw query sorted, we can convert it to use Eloquent so you are returned an Eloquent Collection.
Again, first start by simply getting all twits:
$twits = Twit::all();
Next we need to use the whereIn method:
$twits = Twit::whereIn('user_id', array(2, 3, 4))->get();
But instead of passing an array of user ids, we need to pass a Closure so we can do the subselect:
$twitss = Twit::whereIn('user_id', function($query)
{
$query->select('follow_id')
->from('user_follows')
->where('user_id', '1');
})->get();
And finally we can pass in the current user to include the current user's posts:
$twits = Twit::whereIn('user_id', function($query)
{
$query->select('follow_id')
->from('user_follows')
->where('user_id', '1');
})->orWhere('user_id', '1')->get();
Now you should be returned a Collection of twits from the current user and all the users that the current user follows.
And finally you would just replace the 1 with Auth::user()->id to find the current user.
Hope that helps! :)

Resources