Eager Loading not loading correctlly - php

Something strange happening when using eager loading.
For example i want the get all authors related with a user, and everyhing was working well, but when i use eager loading it doesnt work well.
Example:
Users:
- id;
- name
- ...
Authors:
- id
- user_id
- ...
Model User:
public function authorsProfile()
{
return $this->hasMany(Author::class, 'user_id', 'id');
}
My controller:
$user = Auth::user();
//Get all users and the authors that is related with it (Not working well)
dd( $user->with('authorsProfile')->get());
//Get all authors that is related with this user (Working well)
dd( $user->authorsProfile);
In my case is supposed to give me only the authors related with the current authenticated user, but for some reason when i try to use eagerloading for some reason is getting all users and there relation (authors)...
Does someone have a idea whats wrong?

Calling ->get() executes a whole new query that fetches all users.
You are looking for lazy eager loading:
$user->load('authorsProfile');

If you've already got the User loaded, using ->with() is not the correct approach. In your case, try ->load():
$user = Auth::user();
$user->load("authorsProfile");
dd($user->authorsProfile); // Should be a Collection
Technically, you don't even need to call ->load(), as attempting to access a relationship that wasn't loaded with ->with() will load it at that point:
$user = Auth::user();
dd($user->authorsProfile); // Should also be a Collection
To make it work like you have it coded, you'd need to call:
$user = Auth::user();
$user = User::with(["authorsProfile"])->where("id", "=", $user->id)->first();
dd($user->authorsProfile);
But, you can see why that would be inefficient; you'd be calling the database again to retrieve a record you already have.
So, lots of way to accomplish this. See what you can get working.

Related

Laravel - Using post method in controller

I'm new to laravel and I'm still trying to learn.
This my code in my controller:
public function index(User $user){
$posts = $user->posts()->with(['user', 'likes']);
return view('users.posts.index', [
'user' => $user,
'posts' => $posts,
]);
}
And this is the code on blade.php
{{$user->name}}
I've also tried checking if the code:
dd($posts = $user->posts()->with(['user', 'likes']));
and it seems fine since it's returning data from the database. With these codes it was supposed to show the User's Post on a different page when you click on the User's name. The problem is that it only shows the user's name but not the user's post. I'm only following a tutorial but the result was different with the tutorial and the one that I'm doing. Can someone please help?
I suppose you are trying to lazy eager load the posts relationship on this $user and load the user and likes relationship for those posts. You can try to use load to load these relationships on the Collection:
$posts = $user->posts->load('user', 'likes');
Or load on the User instance:
$user->load('posts.user', 'posts.likes');
$posts = $user->posts;
If you are not trying to have these relationships loaded on the $user but you just want the posts with their relationships you can eager load them off of the relationship itself:
$posts = $user->posts()->with('user', 'likes')->get();

Calling Laravel relationship with aggregate

If I have a Laravel 5.5 model called User that hasMany Posts and each Post hasMany Hits, is there an aggregate function I can call to get the total number of Hits for a User across all Posts, where the Hit was created in the last week?
It seems like there may be a clever way to do it besides doing something like
$hits = $user->posts()->hits()
and then looping over those hits to check created date.
In this case it seems like raw sql would be better, but I figured there may be an Eloquent way to handle a situation like this.
I think the right solution is just to use a HasManyThrough relationship to grab all the Hit rows, joined through the posts table.
So it'd look like this on the User model (roughly):
return $this->hasManyThrough(
Hit::class,
Post::class
// if you have non-standard key names you can specify them here-- see docs
);
Then when you have your User model you can just call $user->hits to get a collection of all the associated hits through all the user's Posts
You can add the code below to your Post model.
protected static function boot()
{
parent::boot();
static::addGlobalScope('hitCount', function ($builder) {
$builder->withCount('hits');
});
}
It automatically provides a field hits_count whenever you fetch a post.
$post = Post::first();
$hits = $post->hits_count; //Count hits that belongs to this post
You can read the documentation here to customize it to your need.
Set HasManyThrough relation in the User model:
public function hits()
{
return $this->hasManyThrough('App\Models\Hits','App\Models\Posts','user_id','post_id','id');
}
then you can do this:
$reults = $user->hits()->where('hits_table_name.created_at', '>=', Carbon::today()->subWeek())->count();
HasManyThrough Link
Use DB::enableQueryLog(); and DB::getQueryLog(); to see if executed SQL Query is correct;

Trying to get data via related table in Laravel

What I'm trying to do is to show the posts that have been saved by the user in the profile. I will try to explain it as good as possible refering to my code. So:
public function userProfil($id)
I have the profile function which get the data from userprofile table. and inside I have the following code for saved data:
$authed = User::find($id);
$savedarticles = $authed->mysaves;
$allsavings = DB::select("Select * from article where id=$savedarticles->id");
But this code does not work like this anyway. I can do this instead:
$authed = User::find($id);
$savedarticles = $authed->mysaves;
But when I try to get articles from article table with the article_id of mysaves, it does not work such as this:
$allsaved= DB::table('article')->where('id', $savedarticles->article_id);
the error it gives is like:
Property [article_id] does not exist on this collection instance.
although savearticle table has article_id I can output it without the line above and in view I get them as:
#foreach($savedarticles as $savedarticle)
<p>{{$savedarticle}}</p>
#endforeach
it gives me everything that is in the savearticle table and I can get do savedarticle->article_id and get article_id but can't get it in controller.
I am using Laravel 5.4.
The error message Property [article_id] does not exist on this collection instance. means you are trying to get an attribute of a single instance but from a collection.
For example the collection could be like
[$article1, $article2, $article3]
therefore what you tried to do is something similar to
[$article1, $article2, $article3]->article_id
You are trying to get an attribute from a collection instead of a single instance.
As for your query, you can use where in sql statement to search for rows that match any item in an array
$allsaved= DB::table('article')->whereIn('id', $savedarticles->pluck('article_id')->all());
What I have understood is that A USER has many POSTS and a POST belong to an article.
If this is true then you have to do following.
1: In USER model define a relation to get all posts. like below.
public function posts() {
// Foreign key will be a key that is stored in posts table and represent the user MAY BE: user_id
$this->hasMany(Posts::class, 'foreign_key', 'local_key')
}
This will allow you to get all posts belong to a user.
2: In posts, model defines a user relation like below.
public function user() {
$this->belongsTo(User::class, 'foreign_key', 'local_key');
}
This will allow you to get a post User;
3: Now in your controller you will have something like this.
public function show($user_id) {
// find a user with posts as eager loading(to avoid query again)
$user = User::with(['posts'])->where('id', $user_id)->first();
// get all posts that belong to this user
$posts = $user->posts;
}
In controller show($user_id) method you will have a user data as well as user posts data. Now if you want to get a post relations then simply define as below. let say a post belongs to an article as well.
4: In posts, model defines a relation to get an article.
public function article() {
// This will allow you to get a post artcle
$this->belongsTo(Article::class, 'foreign_key', 'local_key');
}
Now you can get the article as well while finding a user. please see below. I am rewriting controller show action to give you a better understanding.
5: Get a user with user_id
public function show($user_id) {
// find a user with posts as eager loading(to avoid query again)
// eager loading for posts & post child, this will give you NOSQL at runtime and all data will come from one query.
$user = User::with(['posts', 'posts.article'])->where('id', $user_id)->first();
// get all posts that belong to this user
$posts = $user->posts;
foreach($posts as $post) {
$article = $post->article; // Child relation of post.
}
}
Hope you will understand the flow, you have to make sure models relation to work it perfectly. If you need further help please let me know.

Laravel (Lumen) - Acting As A User In The Database

When testing I am able to create a model factory and then act as that user e.g.
$users = factory(User::class)->create();
$this->actingAs($users)
Would it be possible for me to act as a user who is already in the database, so say I could reference their ID number and then test as that user?
Try $users = User::find(1); .. where 1 is the correct id.
You can use any Eloquent function such as User::where('name', '=', 'John'); too.
You might have to fix the User namespace like $users = App\Models\User::find(1); or use (import) it.
Note that you can roll back your databases after each test too, or simply use another database altogether.

laravel eloquent relationship hasmany query

i am trying to implement a sql query
select * from user,comments where comments.user_id= user.id
so i create a getcomments method on my user model with following code
public function comments(){return $this->hasMany('Comments')}
and now am accessing the data by
$data = User::find(1)->comments;
but it gave me the data only from comments table (not user and comments )
how can i do this
The Eloquent ORM follows the Active Record pattern. It is a slightly different way to think about modeling and interacting with your data when you come from writing pure sql statements.
Setting up the comments relationship is a good step. Now you need to think about how you interact with your data.
You can get all the information with the following statement:
$user = User::with('comments')->find(1);
With this statement, all of the user information is loaded into the $user object, and all of the comment information is loaded into the $user->comments Collection attribute. This information can be accessed like so:
// get the info
$user = User::with('comments')->find(1);
// display some user info
echo $user->first_name;
echo $user->last_name;
// loop through the comment Collection
foreach($user->comments as $comment) {
// display some comment info
echo $comment->text;
}
The with('comments') section tells the query to eager load all the comments for the returned users (in this case, just the one with id 1). If you didn't eager load them, they would be lazy loaded automatically when you try to access them. The above code would work exactly the same without the with('comments'). Eager loading becomes more important when your loading multiple parent records, though, instead of just one, as it solves the N+1 problem. You can read about eager loading here.
Caution (the reason I added a new answer):
User::find(1)->with('comments')->get();, as otherwise suggested, is not going to provide the information you're looking for. This will actually end up returning all your users with their comments eager loaded. Here is why:
First, User::find(1) is going to return the one user with an id of 1, which is good. However, it then calls with('comments') on this model, which actually creates a new query builder instance for the users table. Finally, it calls get() on this new query builder instance, and since it doesn't have any constraints on it, it will return all the users in the table, with all the comments attached to those users eager loaded.
You are fetching just comments of the user which you select by id. You should this;
User::find(1)->with('comments')->get();
First find a user. Then access all user data, and all comments of user.
$data = User::find(1);
//or you can use eager loading for more performance. thanks for #Özgür Adem Işıklı
$data = User::with('comments')->find(1);
//access user data
$data->id;
$data->email; //etc.
//user's comments:
foreach($data->comments as $comment) {
//access comment detail
$comment->id;
$comment->title;
}

Categories