Automatic eager loading? - laravel

Rather than doing something like this (which I do dozens of times across the site):
$posts = Post::with('user')
->with('image')
->get();
Is it possible to automatically call with('image') whenever with('user') is called? So in the end, I could do just:
$posts = Post::with('user')
->get();
And still eager load image?

Add the following in your model:
protected $with = array('image');
and that should do the trick.
The $with attribute lists relations that should be eagerly loaded with every query.

Here another solution that works like a charm !
class Post extends Model {
protected $table = 'posts';
protected $fillable = [ ... ];
protected $hidden = array('created_at','updated_at');
public function user()
{
return $this->belongsTo('App\Models\User');
}
public function userImage()
{
return $this->belongsTo('App\Models\User')->with('image');
}
}
$posts = Post::with('userImage')->get();
Using this you can still use your user posts $posts = Post::with('user')->get(); whenever you don't want to make an additional call to retrieve images ..

Related

Laravel 9 - specular model relations not working

i can't figure out why my eloquent relationship is not working.
I have the grid and details tables related (es. grid.id = details.order_id). This works for first 2 tables but not for other 2 that are almost a copy of the first.
Working relationship code :
OrderGrid model:
use HasFactory;
protected $table = 'orders_grid';
protected $fillable = [
'user_id',
// more data
];
public function order_detail()
{
return $this->hasMany('App\Models\OrderDetail');
}
OrderDetail model:
use HasFactory;
protected $table = 'orders_detail';
protected $fillable = [
'order_id',
// more data
];
public function order_grid()
{
return $this->belongsTo('App\Models\OrderGrid', 'order_id', 'id');
}
In controller
$data_details = OrderDetail::where('order_id', $id)->get();
in view
$data_details->first()->order_grid->id
// so i can get the ID from the related table
Now i have two more very similar models which relations are not working:
OrderGrid model 2
use HasFactory;
protected $table = 'orders_grid_jde';
protected $fillable = [
'total_order',
// more data
];
public function detail_jde()
{
return $this->hasMany('App\Models\OrderDetailJde');
}
OrderDetail model 2 :
use HasFactory;
protected $table = 'orders_detail_jde';
protected $fillable = [
'order_jde_id',
];
public function grid_jde()
{
return $this->belongsTo('App\Models\OrderGridJde', 'order_jde_id', 'id');
}
In controller:
$data_details = OrderDetailJde::where('order_jde_id', $id)->get();
In view:
$data_details->first()->order_jde_id->id
This is not working, if i dump($data_details->first()) relations is empty array, not as in the screen shown before
Another weird behaviour on working relation: if i dump $data_details->first() in the controller i don't see the relation, but if i do it in view it shows as in the screen.
Sorry for the long post, tried to be understandble as possible
In your Controller call the relation like this:
$data_details = OrderDetailJde::where('order_jde_id', $id)->with("grid_jde")->get();
In view:
$data_details->first()
It will give all the detils.
It should work now.
In the view where you have the error, you are accessing the directly to the field named order_jde_id instead of the relationship grid_jde.
Tips:
You should eager load the relationships if you are planning to use these in the view.
$books = Book::with('author')->get();
https://laravel.com/docs/9.x/eloquent-relationships#eager-loading

Laravel : Can appends attributes lead to a n+1 problem?

Introduction
I start using appends attributes within models during fall 2019. At start, it was simply to run function that will join two column, but now, I use them to make some analysis on certain models before sending it to the frontend. Now, I'm suspecting this to lead to a n+1 problem. So, let check on some use of the appends attributes.
Basic usage
<?php
namespace App;
class User extends Authenticatable
{
protected $dates = ['deleted_at'];
protected $table = 'users';
protected $appends =
[
'full_name',
];
protected $fillable =
[
'username',
'first_name',
'last_name',
];
getFullNameAttribute()
{
return $this->first_name.' '.$this->last_name;
}
}
So, that let you append the full name, we know that, but under the hood?
$user = User::find($id);
Using appends with the above model retrieve is almost incensitive for any application. If we push it a little bit, what really happen?
$users = User::get();
Now, is the query to the database is the same as if we don't append anything? In other word, is the appending happen during the query to DB or after? Whatever, you need the full name and if it's not in the Database or the server, you will do it in the frontend, so that need to happen anyway. Which of the three is the fastest is for another question.
A bit more complexe usage
namespace App;
class User extends Authenticatable
{
protected $dates = ['deleted_at'];
protected $table = 'users';
protected $appends =
[
'full_name',
'last_post',
];
protected $fillable =
[
'username',
'first_name',
'last_name',
];
protected $hidden =
[
'password',
'remember_token',
];
public function posts()
{
return $this->hasMany('App\Post', 'user_id');
}
getFullNameAttribute()
{
return $this->first_name.' '.$this->last_name;
}
getLastPostAttribute()
{
return $this->posts()->last()
}
}
Now it's obvious that if I do $users->get(); it will result into a n+1 problem. But if I do this $users->with('posts')->get(); Do I have the n+1 problem or worst?
If your not use with n+1 problem
The n+1 problem is when you create a query that will need to make the initial call to DB and one call for each object into the array. $users->get() will get all users and then, since I append the last_post, since post where not retrieved, it will make a DB call for each of the models.
What I want to know, it's in the second case $users->with('posts')->get(), when Laravel will append last_post, will it know that it already have all the posts? So it doesnt need to load them a second time for each users?
So this attribute will cause n+1 queries no matter what you preload
getLastPostAttribute()
{
return $this->posts()->last()
}
By accessing posts() as a method you are creating a new query builder instance.
Replace it with return $this->posts->last() and then you can preload you posts relationship and avoid your extra queries
If you are unsure if you are getting n+1 issues you can require this great package from beyondcode beyondcode/laravel-query-detector which will inform you of any n+1's you have.
https://github.com/beyondcode/laravel-query-detector
To fix your n+1...
$users = Users::with(['posts'])->get();
public function getFullNameAttribute()
{
return $this->first_name.' '.$this->last_name;
}
public function getLastPostAttribute()
{
return $this->posts->last(); //update to not use builder instance
}

Eager load hasMany & belongsTo (circular reference/infinite loop)

UPDATE (SOLUTION)
If you need ->user relationship from one of the $image inside $user->images, then $user variable is already available cause you loaded the ->images from it!
Don't use protected $with Eloquent property. It's an anti-pattern.
Instead explicitly eager load relationships on-demand from where/when it's needed (Note: it should not prevent you to keep things DRY!)
If you really need/want to, see #nicksonyap answer. It does the trick (I believe – not tested).
ORIGINAL
I'm running into what I believe is a simple problem:
I have a User object that has many Images
Image belongs to User... (inverse relation)
My problem is that I want to eager load both the images() on the User model and the user() on the Image model. To do so, I just setup a $with property as explained in the docs.
My User model:
class User extends EloquentModel {
protected $with = ['images'];
public function images()
{
return $this->hasMany(Image::class);
}
}
My Image model:
class Image extends EloquentModel {
protected $with = ['user'];
public function user()
{
return $this->belongsTo(User::class);
}
}
But when performing:
$user = User::find(203);
This results in an infinite loop (php segmentation fault). There must be some kind of circular reference that I am not able to locate:
[1] 85728 segmentation fault
EDIT 2016/02
This is the simplest "Workaround" I found:
// User.php
public function setRelation($relation, $value)
{
if ($relation === 'images') {
foreach ($value as $image) {
$image->setUser($this);
}
}
return parent::setRelation($relation, $value);
}
There is a without() method: https://laravel.com/api/5.8/Illuminate/Database/Eloquent/Builder.html#method_without
Placing without() on both sides of a relationship worked.
class Property extends EloquentModel {
protected $with = ['images'];
public function images()
{
return $this->hasMany(Image::class)->without('property');
}
}
class Image extends EloquentModel {
protected $with = ['property'];
public function property()
{
return $this->belongsTo(Property::class)->without('images');
}
public function getAlt()
{
return $this->property->title;
}
}
UPDATE:
Even though using without() easily avoid the infinite loop issue, through years of experience with Laravel I realize it is bad practice to set $with in the model as it causes relationships to always load. Hence leading to circular reference/infinite loop
Rather, always use with() to explicitly specify necessary relationships to be eager loaded, however deep necessary (relationship of relationship)
For example:
$user = User::with('images' => function ($query) {
$query->with('property' => function ($query) {
$query->with('deeperifneeded' => function ($query) {
//...
});
});
]);
Note: May need to remove without()
When you try to find a Property, that property eager loads all the images it has and every Image eager loads the property it belongs to, which is the property you try to find, which will again start to eager load all the images it has. etc...
The way I would resolve this problem is by not eager loading inside the models, but by eager loading when calling the models.
so using the following:
$prop = Property::with('images')->find(203);
while removing this line in the Property model:
protected $with = ['images'];
And this line in the Image model:
protected $with = ['property'];
I hope this solution works for you.

How do I prevent fetching data multiple times inside a Repository?

My EloquentUserRepository (concrete implementation) has some methods like getCompanies($userId) and getProfile($userId), for example.
In both cases, before returning stuff, they fetch the user, like so:
$user = User::find($userId);
So, when I need to call those two methods in the same request, the data is fetched twice.
Am I missing something or am I using repositories wrongly?
What if you approached it from the relationship side instead? I'm assuming that Companies live in a different table. So could you acquire the companies with something like this?
<?php
class User extends Model {
protected $table = 'users';
protected $fillable = ['name', 'email', 'avatar'];
protected $hidden = ['password', 'remember_token'];
public function companies()
{
return $this->hasMany('App\Company', 'user_id');
}
}
Then you could find the companies with:
$user = User::find($userId)->load('companies')->get();
and you'd already have the companies loaded.
If the profile is in a different table you could do a similar relationship, like this:
<?php
class User extends Model {
protected $table = 'users';
protected $fillable = ['name', 'email', 'avatar'];
protected $hidden = ['password', 'remember_token'];
public function profile()
{
return $this->hasOne('App\Profile', 'user_id');
}
}
I'm not certain if this is exactly what you are looking for, but it's the approach I like to use when finding items that are related to a particular model.

Laravel 4.1 - Eloquent lazy loading and filtering results based on pivot table value

I have 2 models, Sound and Tag:
<?php
class Sound extends Eloquent{
protected $table = 'sounds';
protected $guarded = array('id');
public function tags()
{
return $this->belongsToMany('Tag');
}
}
<?php
class Tag extends \Eloquent {
protected $table = 'tags';
protected $guarded = array('id');
public function sounds()
{
return $this->belongsToMany('Sound');
}
}
In the controller I do lazy loading and it works as expected; the sounds are fetched and each of them has tags attached. The code below:
$sounds = Sound::with(array('tags'))->get();
I now want to filter the sounds by a tag_id (get only the sounds that have a particular tag attached) and I'm not sure if it's possible in Eloquent.
I tried eager load constraints but these would only specify the condition based on which a tag is attached to a sound or not, and not actually filter the sounds.
Should I use the query builder instead?
Thank you.
try this code
$sounds = Sound::whereHas('tags', function($q) use($tag_id)
{
$q->where('tags.id', '=', $tag_id);
})->get();
so you only get sounds with a certain tag.
angorus's answer is perfectly fine. Just some additional information on this:
If you need functions like this more often, it can be helpful to put them in a query scope within your model.
<?php
class Sound extends Eloquent{
//....
public function scopeHasTag($query, $tag_id)
{
return $query->whereHas('tags', function($q) use ($tag_id)
{
$q->where('tags.id', '=', $tag_id);
});
}
}
Then in the controller, to only get those Sounds that have a certain tag associated you can do something like:
$sounds = Sound::with('tags')->hasTag($tag_id)->get();

Resources