Laravel - Eager Loading BelongsToMany Relationship - php

I have one to many relationship between two entities/tables.
/**
* Get all of the products.
*/
public function products()
{
return $this->belongsToMany(Product::class)->select(
[
'products.id',
'products.title',
'products.sku',
'automation_products.automation_id as auto_id',
'display_order',
]
)->orderBy('display_order');
}
When I want eager load this relationship, it seems like there are duplicate queries running in the background. I use this code to eager load my relation:
$automation = \App\Models\Automation::with('products')->whereId(1)->get()->first();
dump($automation->products()->get());
dump($automation->products()->get());
dump($automation->products()->get());
is there something I am missing?
Thanks for the reply.

Eager loading load relation into model property.
You can access this property like $automation->products - no matter how many times she is called - query will be executed ONE time with eager load.
But, when you call like ->products()->get() - eloquent execute query because you tell "get() relation products() NOW"

Related

Laravel HasOne relation is eager loading all models into memory rather than the specified oldest()

I am trying to load the next due appointment from a relation. The relation is HasMany so my function looks like this:
public function nextAppointment(): HasOne
{
return $this->hasOne(Appointment::class, 'some_model_id')
->whereNull('completed_at')
->oldest('starts_at');
}
This is working fine however, locally, I have debugdhbar installed and the SQL query this is doing is a where in. This results in ALL the appointments being eager loaded into memory and I end up seeing that over 100 appointments are being loaded for a simple check against the latest. The SQL looks like this:
select * from "appointments" where "completed_at" is null and "appointments"."some_model_id" in ('00cb2664-2aec-4600-a3ca-873dbb5f81f3', '04b62cc7-9ec7-4613-af13-3bc53f9b3538', '109fce77-0fd4-4478-b30d-0c95468d1037', '11b28a27-020d-46f8-b498-51ec152192a2', '11dee373-ec59-4804-897e-2bc5094a3785', '15614002-2414-488d-b639-6410f1c32004', '19d43627-10c5-4708-861d-9394a6ee9b69', 'fffc6b29-fbac-4b6a-80bf-781a2e720c38') order by "starts_at" asc
How can I optimize this relation so it only loads the latest appointment into memory? The controller looks like this:
return SomeModel::with(['nextAppointment'])->paginate(request()->input('size', 10));
I think you want to use oldestOfMany() Link to Docs
public function nextAppointment(): HasOne
{
return $this->hasOne(Appointment::class, 'some_model_id')
->whereNull('completed_at')
->oldestOfMany('starts_at');
}

Laravel with() works only for first record (only for record with id 1)

Imagin situation where in laravel User Eloquent model I have this kind of relationship functions:
public function serviceProvider(){
return $this->belongsTo('App\Http\Models\Users\ServiceProvider');
}
public function company(){
return $this->serviceProvider->company();
}
I have this kind of relationship in serviceProvider model:
public function company()
{
return $this->morphOne('App\Http\Models\Companies\Company', 'companyable');
}
Now in controller I write something like that:
User::where('status', '=', 1)->with('company')->get();
I know it sounds crazy but it only works for record where user's id = 1, for the rest of the record it just returns company : null
I have no idea what is going on here, Maybe I am doing something wrong? what is the problem?
If I write something like that: $user->company, it works, It works for each user, I mean for
$user = User::where('id', '=', "any id")->first;
so problem is with with()
I'm a little surprised this is working at all to be fair.
Since your User a relationship for serviceProvider set up and that has a relationship for company you don't need to have a company relationship in User.
If you want to eager/lazy load nested relationships you can just use dot notation e.g.
User::with('serviceProvider.company')->where('status', 1)->get();
Lazy load docs (scroll down to Nested Eager Loading)

Laravel Eager Loading Relation With Additional ->where()

This is my relation method in my model:
public function relation()
{
return $this->belongsTo('App\Table', 'table_2_id', 'table_2_id')->where('table_1_id', $this->table_1_id);
}
So, both the model above and Table2 have the same column, but there is the possibility of many entries, so I want to then filter on a second column that is shared by both tables, table_1_id.
It seems to work perfectly on the command line, but when eager loading the relationship is empty.
If I remove the additional ->where() from the relationship method then the eager loading works.
The way I am eager-loading is by doing
->with('relation.nestedRelation');
I've also tried
->with(['relation' => function ($q) {
$q->with('nestedRelation');
}])
I've just tried adding it as an attribute to see if that helped but still no joy.
You can use the whereColumn() function :
->whereColumn('table_1.table_1_id', 'table_2.table_1_id')
Or you can use that :
public function filterRelation(){
return $this->relation->where('table_1_id', $this->table_1_id);
}

Eager load ONE from Many to Many relationship

I have a many to many relationship for users and roles. A user can have multiple roles, but I only want with to grab the FIRST role.
Consider the following code:
User::with('roles')->get()
Works great for all roles, but I only want the first role.
I've set this up in my model but doesn't work:
public function role()
{
return $this->roles()->first();
}
How do I load with for only the first result?
You should be able to call first directly on the eager loaded relationship like this:
User::with(['roles' => function ($query) {
$query->first();
})->get();
first() actually executes the query and returns the results as a collection. Relationships must return a query builder, which can then be chained or executed, so using first() in a relationship won't work.
UPDATE
I realised you want to use role in with, so you need to create a relationship to do that. Create a new relationship on your User model (you can use any limit described in the docs, not just oldest()):
public function role()
{
return $this->hasOne('App\Role')->oldest();
}
And then you can use it in with:
$users = User::with('role')->get();

1 to 1 inverse relationship "belongsTo" giving a collection laravel instead of model

In my Profile model I setted this relationship
public function lease()
{
return $this->belongsTo(Lease::class, 'lease_id', 'id');
}
And in my Lease model I seeted this way
public function profile()
{
return $this->hasOne(Profile::class, 'lease_id', id);
}
As longs as I know in laravel you could do
$profile = factory(App\Profile::class)->create();
$profile->lease()->get();
And then responds correctly with the model inside of a collection
And if I do
$profile->lease
Responds correctly directly with the model
It isn't supposed that dynamic propertis execute the query right away like a shortcut of ->lease()->get()? Why it gives different formatted results?
When you are calling get on a builder you are getting a collection always. When you call first on a builder like that you will get a model or null. The dynamic property for the relationship, based upon the relationship object, will either query with get or first respectively when it loads it. Which is why $model->relationship is returning you the result you expect.
The relationships that are singular, cause a find and the ones that are many cause a get.
Laravel 5.4 - Docs - Eloquent - Relations - Relationship Methods vs Dynamic Properties

Categories