laravel with() method versus load() method - laravel

I really tried to understand the difference between the with() method and the load() method, but couldn't really understand.
As I see it, using the with() method is "better" since I eager load the relation. It seems that if I use load() I load the relation just as if I would use the hasMany() (or any other method that relates to the relation between objects).
Do I get it wrong?

Both accomplish the same end results—eager loading a related model onto the first. In fact, they both run exactly the same two queries. The key difference is that with() eager loads the related model up front, immediately after the initial query (all(), first(), or find(x), for example); when using load(), you run the initial query first, and then eager load the relation at some later point.
"Eager" here means that we're associating all the related models for a particular result set using just one query, as opposed to having to run n queries, where n is the number of items in the initial set.
Eager loading using with()
If we eager load using with(), for example:
$users = User::with('comments')->get();
...if we have 5 users, the following two queries get run immediately:
select * from `users`
select * from `comments` where `comments`.`user_id` in (1, 2, 3, 4, 5)
...and we end up with a collection of models that have the comments attached to the user model, so we can do something like $users->comments->first()->body.
"Lazy" eager loading using load()
Or, we can separate the two queries, first by getting the initial result:
$users = User::all();
which runs:
select * from `users`
And later, if we decide that we need the related comments for all these users, we can eager load them after the fact:
$users = $users->load('comments');
which runs the 2nd query:
select * from `comments` where `comments`.`user_id` in (1, 2, 3, 4, 5)
...and we end up with the same result, just split into two steps. Again, we can call $users->comments->first()->body to get to the related model for any item.
Why use load() vs. with()? load() gives you the option of deciding later, based on some dynamic condition, whether or not you need to run the 2nd query. If, however, there's no question that you'll need to access all the related items, use with().
The alternative to either of these would be looping through the initial result set and querying a hasMany() relation for each item. This would end up running n+1 queries, or 6 in this example. Eager loading, regardless of whether it's done up-front with with() or later with load(), only runs 2 queries.

As #damiani said, Both accomplish the same end results—eager loading a related model onto the first. In fact, they both run exactly the same two queries. The key difference is that with() eager loads the related model up front, immediately after the initial query (all(), first(), or find(x), for example); when using load(), you run the initial query first, and then eager load the relation at some later point.
There is one more difference between With() & load(), you can put the conditions when using with() but you can't do the same in case of load()
For example:
ProductCategory::with('children')
->with(['products' => function ($q) use($SpecificID) {
$q->whereHas('types', function($q) use($SpecificID) {
$q->where('types.id', $SpecificID)
});
}])
->get();

#damiani Explanied difference between load() and with() as well but he said load() is not cacheable so I wanna say couple words about it.
Let assume we have a blog post and related with comments. And we're fetching together and caching it.
$post = Cache::remember("post.".$slug,720,function()use($slug){
return Post::whereSlug($slug)->with("comments")->first();
});
But if there is a new comment and we want to display it immediately, we have to clear post cache and fetch post and comments together again. And that causes unnecessary queries. Lets think there are another queries for tags, media, contributors of the post etc. it will increase amount of resource usage..
public function load($relations)
{
$query = $this->newQueryWithoutRelationships()->with(
is_string($relations) ? func_get_args() : $relations
);
$query->eagerLoadRelations([$this]);
return $this;
}
As you can see above when we use the method it loads given relation and returns model with fetched relation. So you can return it outside of a callback.
$post = Cache::remember("post.".$slug,720,function()use($slug){
return Post::whereSlug($slug)->first();
});
$post = Cache::remember("post.relation.images.".$slug,720,function()use($post){
return $post->load("images");
});
$post = Cache::remember("post.relation.comments".$slug,720,function()use($post){
return $post->load("comments");
});
So if we load them seperatly, next time when some of them updated all you need to do clear specific relation cache and fetch it again. No need to fetch post, tags, images etc. over and over.

Related

What is the different between laravel WITH and LOAD [duplicate]

I really tried to understand the difference between the with() method and the load() method, but couldn't really understand.
As I see it, using the with() method is "better" since I eager load the relation. It seems that if I use load() I load the relation just as if I would use the hasMany() (or any other method that relates to the relation between objects).
Do I get it wrong?
Both accomplish the same end results—eager loading a related model onto the first. In fact, they both run exactly the same two queries. The key difference is that with() eager loads the related model up front, immediately after the initial query (all(), first(), or find(x), for example); when using load(), you run the initial query first, and then eager load the relation at some later point.
"Eager" here means that we're associating all the related models for a particular result set using just one query, as opposed to having to run n queries, where n is the number of items in the initial set.
Eager loading using with()
If we eager load using with(), for example:
$users = User::with('comments')->get();
...if we have 5 users, the following two queries get run immediately:
select * from `users`
select * from `comments` where `comments`.`user_id` in (1, 2, 3, 4, 5)
...and we end up with a collection of models that have the comments attached to the user model, so we can do something like $users->comments->first()->body.
"Lazy" eager loading using load()
Or, we can separate the two queries, first by getting the initial result:
$users = User::all();
which runs:
select * from `users`
And later, if we decide that we need the related comments for all these users, we can eager load them after the fact:
$users = $users->load('comments');
which runs the 2nd query:
select * from `comments` where `comments`.`user_id` in (1, 2, 3, 4, 5)
...and we end up with the same result, just split into two steps. Again, we can call $users->comments->first()->body to get to the related model for any item.
Why use load() vs. with()? load() gives you the option of deciding later, based on some dynamic condition, whether or not you need to run the 2nd query. If, however, there's no question that you'll need to access all the related items, use with().
The alternative to either of these would be looping through the initial result set and querying a hasMany() relation for each item. This would end up running n+1 queries, or 6 in this example. Eager loading, regardless of whether it's done up-front with with() or later with load(), only runs 2 queries.
As #damiani said, Both accomplish the same end results—eager loading a related model onto the first. In fact, they both run exactly the same two queries. The key difference is that with() eager loads the related model up front, immediately after the initial query (all(), first(), or find(x), for example); when using load(), you run the initial query first, and then eager load the relation at some later point.
There is one more difference between With() & load(), you can put the conditions when using with() but you can't do the same in case of load()
For example:
ProductCategory::with('children')
->with(['products' => function ($q) use($SpecificID) {
$q->whereHas('types', function($q) use($SpecificID) {
$q->where('types.id', $SpecificID)
});
}])
->get();
#damiani Explanied difference between load() and with() as well but he said load() is not cacheable so I wanna say couple words about it.
Let assume we have a blog post and related with comments. And we're fetching together and caching it.
$post = Cache::remember("post.".$slug,720,function()use($slug){
return Post::whereSlug($slug)->with("comments")->first();
});
But if there is a new comment and we want to display it immediately, we have to clear post cache and fetch post and comments together again. And that causes unnecessary queries. Lets think there are another queries for tags, media, contributors of the post etc. it will increase amount of resource usage..
public function load($relations)
{
$query = $this->newQueryWithoutRelationships()->with(
is_string($relations) ? func_get_args() : $relations
);
$query->eagerLoadRelations([$this]);
return $this;
}
As you can see above when we use the method it loads given relation and returns model with fetched relation. So you can return it outside of a callback.
$post = Cache::remember("post.".$slug,720,function()use($slug){
return Post::whereSlug($slug)->first();
});
$post = Cache::remember("post.relation.images.".$slug,720,function()use($post){
return $post->load("images");
});
$post = Cache::remember("post.relation.comments".$slug,720,function()use($post){
return $post->load("comments");
});
So if we load them seperatly, next time when some of them updated all you need to do clear specific relation cache and fetch it again. No need to fetch post, tags, images etc. over and over.

Can I use with() instead Load() Laravel eloquent

$school=$school->teachers->load('TeacherCourses');
This will return all teachers with their courses in my laravel project..
can I use with() function like below?? if I cant then how ?
$school=$school->teachers->with('TeacherCourses');
Yes, but the correct syntax would be different. load() is used after you've retrieved the model from the Database, but with() is used prior to that.
$school = School::first();
$school->teachers->load('teacherCourses');
̶T̶h̶i̶s̶ ̶w̶o̶n̶'̶t̶ ̶w̶o̶r̶k̶,̶ ̶a̶s̶ ̶̶t̶e̶a̶c̶h̶e̶r̶s̶̶ ̶s̶u̶g̶g̶e̶s̶t̶s̶ ̶a̶ ̶C̶o̶l̶l̶e̶c̶t̶i̶o̶n̶,̶ ̶w̶h̶i̶c̶h̶ ̶d̶o̶e̶s̶n̶'̶t̶ ̶h̶a̶v̶e̶ ̶a̶ ̶̶l̶o̶a̶d̶(̶)̶̶ ̶m̶e̶t̶h̶o̶d̶.̶ Corrected by #patricus; Collections do have a load() method, so ->teachers->load('teacherCourses') will work. Alternative syntax would be:
$school->load('teachers.teacherCourses');
The more efficient way to write this is:
$school = School::with('teachers.teacherCourses')->first();
This will eager-load both the teachers and teacherCourses nested relationships, in a minimal number of database calls.
Edit
Route Model Binding will do this behind-the-scenes:
$school = School::findOrFail($schoolId);
Since it's only loading a single Model, using load() is perfectly acceptable. with() loads the relationships via a single query, for a total of 1 + (# of relationships loaded) queries (3 in your case, school, teachers and teacherCourses). load() will do the same thing, so there's no need to use with() over load() in this case.

Is Laravel sortBy slower or heavier to run than orderBy?

My concern is that while orderBy is applied to the query, I'm not sure how the sortBy is applied?
The reason for using sortBy in my case is because I get the collection via the model (i.e. $user->houses->sortBy('created_at')).
I'm just concerned about the performance: is sortBy simply looping each object and sorting them?, or is Laravel smart enough to simply transform the sortBy into an orderBy executed within the original query?
You need orderBy in order to perform a SQL order.
$user->houses()->orderBy('created_at')->get()
You can also eager load the houses in the right order to avoid N+1 queries.
$users = User::with(['houses' => function ($query) {
return $query->orderBy('created_at');
}])->get();
$orderedHouses = $users->first()->houses;
The sortBy method is applied to the Collection so indeed, it will looping each objects.
The orderBy() method is much more efficient than the sortBy() method when querying databases of a non-trivial size / at least 1000+ rows. This is because the orderBy() method is essentially planning out an SQL query that has not yet run whereas the sortBy() method will sort the result of a query.
For reference, it is important to understand the difference between a Collection object and a Builder object in Laravel.
A builder object is, essentially, an SQL query that has not been run. In contrast, a collection is essentially an array with some extra functionality/methods added. Sorting an array is much less efficient than pulling the data from the DB in the correct format on the actual query.
example code :
<?php
// Plan out a query to retrieve the posts alphabetized Z-A
// This is still a query and has not actually run
$posts = Posts::select('id', 'created_at', 'title')->orderBy('title', 'desc');
// Now the query has actually run. $posts is now a collection.
$posts = $posts->get();
// If you want to then sort this collection object to be ordered by the created_at
timestamp, you *could* do this.
// This will run quickly with a small number or rows in the result,
// but will be essentially unusable/so slow that your server will throw 500 errors
// if the collection contains hundreds or thousands or objects.
$posts = $posts->sortBy('created_at');

Eager load single item with Eloquent belongsToMany

I have a Batch model, which hasMany Results and belongsTo a Project. The current status of a batch is based on the status of its most recent Result. So, in my batch model I have this:
public function allForProject($pid)
{
$batches = $this
->with(static::$relatedObjects)
->with('current_status')
->where('project_id', '=', $pid)
->get();
return $batches;
}
public function current_status()
{
return $this
->belongsToMany('BehatEditor\Models\Result')
->orderBy('created_at', 'DESC')
->limit(1)
;
}
...So by saying "->with('current_status')" I am trying to eager load only the most recent result for that batch - there may be thousands of them per batch that I do not want to return to the front end.
Now, this doesn't break, but the "limit(1)" actually seems to limit the number of Batches that get returned with a Result. Even though each one of my Batches has 2 results with my test data, when I use limit(1) only one of them comes back with any data. When I use limit(2), only 2 batches come back with a single current_status record (desired) but the rest have an empty array for current_status.
This is a Silex project using Eloquent as an ORM, so Laravel specific methods won't work.
Any help is much appreciated!
UPDATE:
It looks like Eloquent just doesn't support this. see http://irclogs.julien-c.fr/2013-12-19/01:48#log-52b25061a599aafb54008650. I would like to update my question to be how can I cleanly add the raw SQL I need to my query? Can I supply my own method that holds only the SQL needed, or do I need to replace all ORM usage in allForProject()?
Instead of ->limit(1), use ->first().
Update: Misread what you said, this should work.

Is it possible to eager load arbitrary queries in Eloquent?

I'm working in Laravel 4, and I have a Child model with multiple EducationProfiles:
class Child extends EloquentVersioned
{
public function educationProfiles()
{
return $this->hasMany('EducationProfile');
}
}
If I wanted to get all the EducationProfiles for each kid under age 10 it would be easy:
Child::where('date_of_birth','>','2004-03-27')->with('educationProfiles')->all();
But say (as I do) that I would like to use with() to grab a calculated value for the Education Profiles of each of those kids, something like:
SELECT `education_profiles`.`child_id`, GROUP_CONCAT(`education_profiles`.`district`) as `district_list`
In theory with() only works with relationships, so do I have any options for associating the district_list fields to my Child models?
EDIT: Actually, I was wondering whether with('educationProfiles') generates SQL equivalent to:
EducationProfile::whereIn('id',array(1,2,3,4))
or whether it's actually equivalent to
DB::table('education_profiles')->whereIn('id',array(1,2,3,4))
The reason I ask is that in the former I'm getting models, if it's the latter I'm getting unmodeled data, and thus I can probably mess it up as much as I want. I assume with() generates an additional set models, though. Anybody care to correct or confirm?
Ok, I think I've cracked this nut. No, it is NOT possible to eager load arbitrary queries. However, the tools have been provided by the Fluent query builder to make it relatively easy to replicate eager loading manually.
First, we leverage the original query:
$query = Child::where('date_of_birth','>','2004-03-27')->with('educationProfiles');
$children = $query->get();
$eagerIds = $query->lists('id');
Next, use the $eagerIds to filterDB::table('education_profile') in the same way that with('educationProfiles') would filter EducationProfile::...
$query2 = DB::table('education_profile')->whereIn('child_id',$eagerIds)->select('child_id', 'GROUP_CONCAT(`education_profiles`.`district`) as `district_list`')->groupBy('child_id');
$educationProfiles = $query2->lists('district_list','child_id');
Now we can iterate through $children and just look up the $educationProfiles[$children->id] values for each entry.
Ok, yes, it's an obvious construction, but I haven't seen it laid out explicitly anywhere before as a means of eager loading arbitrary calculations.
You can add a where clause to your hasMany() call like this:
public function educationProfilesUnderTen() {
$ten_years_ago = (new DateTime('10 years ago'))->format('Y-m-d');
return $this->hasMany('EducationProfile')->where('date_of_birth', '>', $ten_years_ago)
}

Resources