تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Eager Loading Overview0:00

This next episode might be a bit of a beast, since we're going to dig in and figure out how eager loading works. For example, we have a Post, right? Well, it stands to reason that a Comment belongs to a Post, right? Well, with that in mind, if we want to fetch all Posts, but eager load the Comments associated with each Post in the process, you may know that with Eloquent, we can say Post::with('comments')->get(). We are eager loading the Comments, which offers the benefit of protecting us against that n plus one issue. So yeah, the syntax is incredibly clean, it's useful, it's performant, it's great, but how exactly does it work underneath the hood? That's the subject of this episode. So to begin, well if we want to eager load Comments, then we need a comments table. So why don't we take the first few minutes and do a little bit of setup. We first need a migration.

Creating Comments Migration0:48

So to begin, well if we want to eager load comments, then we need a comments table. So why don't we take the first few minutes and do a little bit of setup. We first need a migration. php artisan make:migration with schema, create comments table, and as we often do, I want this about as basic and simple as possible. We won't even assign this to a User. Instead, we'll just say a Comment belongs to a Post. So we need a post_id that is of the type integer, and that should be a foreign key. Next, maybe we will just do body, and you know what, let's keep it like that, as basic as we can possibly imagine. All right, so let's take a look. We have our Comment model, and if we take a look at the migration, we have the body, we have the post_id, we have a foreign constraint if you want. We also might want to set unsigned on that. Okay, but good enough. So let's go ahead and migrate our database. And then next, maybe we can seed it at the same time. So I'm going to open

Building Factories and Seeders1:40

you want. We also might want to set unsigned on that. Okay, but good enough. So let's go ahead and migrate our database. And then next, maybe we can seed it at the same time. So I'm going to open up our test directory factories. We already have factories for User and Post. Remember, we are using testDummy here. So why don't we set up a new one to describe a Comment. So we know that it has a postId, that will be a factory of App\Post. It also has the body. And that'll just be fake or paragraph. And I think that should do the trick. So now let's generate a seeder php artisan make:seeder for the comments table. And that'll give me within database/seeds, a comments table seeder. And now we'll say 20 times we want to create a Comment. And now if we switch back to the DatabaseSeeder, we just want to call that as well. And you also might want to do something like this, App\Post::truncate(), and then also App\Comment::truncate(). And yeah, you could clean that up,

Defining Post Relationships2:37

seeder, we just want to call that as well. And you also might want to do something like this, App\Post::truncate(), and then also App\Comment::truncate(). And yeah, you could clean that up, extract it to a method if you want, put it within a foreach, but this is fine. We're just cleaning out those tables every time we run php artisan db:seed. Okay, so now if we run php artisan db:seed, it should clear everything out and rebuild that backup. So if I now run php artisan tinker, let's see, use App\Comment. And let's say Comment::all() and cast that to an array. And there we go. Okay, so next, well, if we want to eager load our comments, well, that means within the Post model, we have to define what kind of relationship that is comments, and a Post has many comments, like so. Okay, so if we were to check this out, we can see that these comments we created are just associated with this one Post, you can change that up if you want, by the way, but it doesn't

like so. Okay, so if we were to check this out, we can see that these comments we created are just associated with this one Post, you can change that up if you want, by the way, but it doesn't really matter in this case. So if we now put this up again, and I were to say App\Post with the comments, find the Post that has an ID of 51. Well, let's scroll up and see what we get. We get a Post model that has all the details. But notice that we've also eager loaded all comments that are associated with that Post. This is what we call eager loading. So now that we have everything in place, let's now move to the source code for Eloquent and figure out how this works. And like I said, this is actually a bit of a beast. So if you have trouble understanding this, you're only human. Eloquent is probably easily one of the most complicated components to the Laravel framework. So we'll do our best and see how deep we can dig here. Okay, let's go with the first step.

Tracing the with() Method4:25

human. Eloquent is probably easily one of the most complicated components to the Laravel framework. So we'll do our best and see how deep we can dig here. Okay, let's go with the first step. If I say Post with Comments, what exactly happens when we hit the with method? Okay, well, we have the model method, and you'll see that this is defined here. So we're not hitting any kind of magic method. Next, well, at this point, relations would be equal to comments, right? So we're basically doing something like this. If isString comments, it is, then relations equals func_get_args. At that point, you would just end up with relations equals comments, right? Because func_get_args returns an array of all of the arguments that you pass. Okay, let me return this to what we had before. Next, we create a new instance of the model class. We create a new query. We've already learned about this in previous episodes. So that will give us

let me return this to what we had before. Next, we create a new instance of the model class. We create a new query. We've already learned about this in previous episodes. So that will give us an instance of the EloquentBuilder class. And then we call a with method on that, and then pass through the relations. So once again, this is sort of a channel that just sends your query through to the builder. So now let's take a look at that. We have a with method there. And at this point, we are passing through, don't forget, something like this. So now we do one more quick check, just in case this method is called from elsewhere. And then we parse the relationships. So let's figure out exactly what that means. Well, when we call this, we filter through this array of relationships that we want to overload. So at this point, name would be just the key in the array, so zero. And then constraints is actually just comments. So it's important to

this array of relationships that we want to overload. So at this point, name would be just the key in the array, so zero. And then constraints is actually just comments. So it's important to remember that, yes, you could say post with comments. You could do that. But you could also add a constraint in the process. So I could say post with comments, and then I pass a closure here to specify what my constraint is for that relationship that we should eager load. So Eloquent needs to check for this. So what we can see here is if these two options are possible, well, we need to parse it. And that's exactly what this method does. So notice we do a check here. Well, if the name is actually numeric, then that means that we didn't pass a constraint. That just means we did something like this. So the way Eloquent handles this is, well, it just converts it to something exactly like this. Notice that he creates an anonymous

pass a constraint. That just means we did something like this. So the way Eloquent handles this is, well, it just converts it to something exactly like this. Notice that he creates an anonymous function, and then we reassign everything. So at this point, constraints is equal to comments. But that actually is the name, right? So we reassign that. Name equals constraints. And then the constraints that you didn't pass through here, we're just going to set that to an anonymous function or a closure. And that way, well, as you can see right here, we can treat everything the same once we go to the next step. However, if you do pass a constraint, well, then everything is correct. This check will fail because name is now a string. It's not the key of the array. So if we scroll down, next, we check to see if you're trying to load a nested relationship. And in fact, you may not even know this. Did you know that you could load a User's

key of the array. So if we scroll down, next, we check to see if you're trying to load a nested relationship. And in fact, you may not even know this. Did you know that you could load a User's posts? Something like this. But if you did this as well, we use this dot notation once again. What this is telling Eloquent is, yes, we want to eager load all posts that are associated with the User, but we also want to eager load all comments that are associated with the posts in the process. You may not have known that you could do that. So we check for that. And if we take a look here, you'll see that we explode on a dot. But in this case, we didn't even get that far. So we just return here directly. Okay. So now, finally, we've done all the parsing we need to. So we update this little results array to the point that this would mean results comments equals, well, that closure that we had here. So we just update that results array. And that, in fact,

So we update this little results array to the point that this would mean resultsComments equals, well, that closure that we had here. So we just update that results array. And that, in fact, is what we return. Cool. So not too difficult at all. Let's go back to that with method. Eager is now something like this. It's an array of arrays. I just want to make sure we're all on the same page here. This should more or less be what the eagers variable is equal to at this point. So now we update the eagerLoad property on this object. And we just use array_merge to make sure that if there's already something assigned there, we don't replace it entirely. We just merge them together. And that's it. So now we've updated our eagerLoad property and we return. Okay. So we've handled Post with comments. We've taken care of that part. The next step is, well, we want to fetch the resultsSet. Right. So now we need to figure out when we call the get method, how does

How get() Loads Relations9:30

handled Post with comments. We've taken care of that part. The next step is, well, we want to fetch the results set. Right. So now we need to figure out when we call the get method, how does that handle the process of eager loading these relationships? Let's take a look. Because we are returning this, the builder object, that means the get method is located here. So let's see what we're doing. All right. The first step, we get the models. So let's see what's happening here. And we already reviewed a little bit of this in the query builder episode. So notice we fetch the results and we just get a fresh set of rows and we pass through our columns that we want to get. In our case, we didn't specify any. So we're just fetching everything, a select star. Now, don't forget, and we're going to see the output of this in just a second, but just to refresh your memory. Well, we already talked about the getFresh method. Do you remember all of this where we run the

forget, and we're going to see the output of this in just a second, but just to refresh your memory. Well, we already talked about the getFresh method. Do you remember all of this where we run the select query? We create the select. So at this point, it would be like select * from posts. And then we attach any bindings that we don't have. So this is all that stuff that we reviewed in that episode. So now that means at this point, if I switch back, we should have an array of Post objects. Let's dd and check that out. If I come back, let's go into php artisan tinker and let's run that exact query once again. Okay, so if I scroll up a ways, there you go. We have an array of arrays for each of the posts. So now think about it. When we normally do this, we get like an Eloquent collection and response, right? But at this point, we just have an array of arrays. That's exactly what you get from a general PDO database query. So at some point, it looks like we need to,

collection and response, right? But at this point, we just have an array of arrays. That's exactly what you get from a general PDO database query. So at some point, it looks like we need to, well, sort of like hydrate this array into a collection of Eloquent models. So let's figure out how that's done. Well, we get our database connection, but notice this exact method name, hydrate. So the way the hydrate method on Eloquent works is you give it an array of arrays, and what that will do is filter through them and for each one, build up a new model instance. Let me show you. If we take a look, at this point, items is equal to all of these results that we have right here. And then we say, well, we're going to create a new eloquent collection, and we pass through those. So now at this point, yes, we have an Illuminate collection object that contains those array items, but that's not enough yet. So you'll see that we map over each item in

and we pass through those. So now at this point, yes, we have an Illuminate collection object that contains those array items, but that's not enough yet. So you'll see that we map over each item in the collection, and for each one, we create a new model. So this is sort of like new Post, and then you pass through the attributes. That's basically what we're doing there. So now if I switch back, we finally call the all method because we're not quite done yet. So we call the all method, which basically removes the collection wrapper. Now we just have an array of Post models at this point. Okay, so I'm going to switch back to where we were before. And by the way, in phpStorm, I'm just hitting command and a closing curly brace to go back. Definitely memorize that one because it'll save you a lot of time. Okay, so now we have an array of models, and in fact, let me just show you what that looks like at this point. php artisan

back. Definitely memorize that one because it'll save you a lot of time. Okay, so now we have an array of models, and in fact, let me just show you what that looks like at this point. php artisan tinker, we run the query again, and there you go. We have all of those various Post models. All right, so now we check to see, well, first, was anything returned from that query? And if so, let's eager load the relationships. This is the key thing here. And then finally, we'll dig into that in just a second. But finally, we do build up our Eloquent collection, we pass through our array of models, and then we're good to go. So now it seems like we've narrowed it down to this point. Here is where we begin eager loading the Comments. Let's figure out how that works. Okay, we have our array of models. And now notice that we do a foreach over the eager load property. And don't forget, that was something like this.

out how that works. Okay, we have our array of models. And now notice that we do a foreach over the eager load property. And don't forget, that was something like this. So that means we'll have one iteration through the foreach where $name is equal to comment and $constraint. In this case, or our case, is just an anonymous empty closure. All right, so let's see, we do a little check here. If there is not a period, meaning you're not trying to fetch a nested relationship, and in our case, that is true, well, we load the relationship onto the model. And then we pass to it our array of Post models, we pass the relationship that we're trying to eager load comments, and then once again, a closure. Okay, let's take a look at that. Eager load the relationship on a set of models. So again, I'm going to warn you, this is where it starts to get a little complicated. So if it sort of goes over your head, that's fine. I'm sure

Eager load the relationship on a set of models. So again, I'm going to warn you, this is where it starts to get a little complicated. So if it sort of goes over your head, that's fine. I'm sure Taylor Otwell would even tell you, for this sort of stuff, you really need to be focusing so you understand all the bits and pieces. But remember, it's not like you need to memorize all this stuff. You're really just coming along for the ride. So you get a basic idea of what's going on, you get to see how other people write their code. That's the best way to learn. It's not like you need to commit this to memory. Alright, so let's see, we get the relationship, what's happening here? Well, this is where we would actually call the relationship on the Post model. So this is where we would call comments. And that's going to return a hasMany relationship. Let me show you. If we dig in here, get relation. Remember, at this point, relation is comments. So we start by

where we would call comments. And that's going to return a hasMany relationship. Let me show you. If we dig in here, getRelation. Remember, at this point, relation is comments. So we start by preparing a new relationship query. And then notice that we pass through the closure here. And this is exactly what I'm talking about this getModel. Well, that's going to return to us our Post model. And then we call $relation. Well, that's comments. So there you go, we're just calling the comments method on the Post model. And as we know, that's going to return whatever we say it returns, which in this case is a hasMany object. Okay, let's see. If we scroll back, we now have our query. Next, we get any nested relations, we aren't worrying about that in this episode, because we don't have any. So we can ignore all of this stuff. And ultimately, we just return that query that we set up. Okay, cool. So I'm

we aren't worrying about that in this episode, because we don't have any. So we can ignore all of this stuff. And ultimately, we just return that query that we set up. Okay, cool. So I'm going to go back to our loadRelation method, we have our hasMany relationship, and then we add the eager constraints. So let's see, let's go to the hasMany objects and add eager constraints. And notice this is where we prepare that query. So think about it. If you had something like Post with Comments, well, what kind of SQL would you write for that you would get your posts, but then to get the comments? Well, really, you'd end up doing something like select * from comments where post_id in, and then you would provide an array of all the postIDs that you got from select * from posts. That's exactly how you would find only the comments that are part of this post. So you'll see, well, this is basically exactly that this query,

you got from select * from posts. That's exactly how you would find only the comments that are part of this post. So you'll see, well, this is basically exactly that this query, add a whereIn clause, where in the post_id for this example, is in this array of all the primary keys from the models. So that would be something, you know, like 51, 52, 53, etc. We're just adding a whereIn clause to the query builder. Not that complicated. So now if we switch back, we added our whereIn eager constraints. Next, call user function. Don't forget. Well, we talked about this, you can do this, or you can add your own constraint, in which case you would say comments, accept your query, and then you would do your query there. So that means because we didn't provide any, well, we learned that Laravel just set the constraints to a closure, right? So we call the closure, but we know that it does absolutely nothing in this case.

didn't provide any, well, we learned that Laravel just set the constraints to a closure, right? So we call the closure, but we know that it does absolutely nothing in this case. So what next? We init the relationship. So what we're going to do here is filter through all of our models in our array here, and we're going to make sure that we assign the relationship now. So if we take a look at that, this is actually an abstract class. We want the implementation for our hasMany relationship. So take a look. For each of the models, set the relationship. So related would be our comments, and then we get a collection of all the comments that we got in return, and we assign that to the model. So now we're starting to get there. Finally, we match these back up. So on a relation class, when we do call get, that's just getting a fresh copy using the query builder that we talked about.

Finally, we match these back up. So on a relation class, when we do call get, that's just getting a fresh copy using the query builder that we talked about. So if we come back, we have our results. Let me show you what that looks like at this point. Let's start up Tinker again, run our query, and now you can see we have an eloquent collection of all of these various Comment objects. And you know what? I think that's about as far as we're going to go. Like I said, this is kind of a beast because there's really so many layers. A very basic ORM isn't the hardest thing in the world to build, but once you get beyond the basics and you need to work on more functionality that honestly your typical developer is going to require for any normal app, well, clearly, as you can see here, this stuff has a way of getting pretty complicated. But mostly, remember, our goal here was not to memorize or reproduce or do anything.

for any normal app, well, clearly, as you can see here, this stuff has a way of getting pretty complicated. But mostly, remember, our goal here was not to memorize or reproduce or do anything. It was mostly to take as much of this stuff in, try to figure out what's going on, and mostly just to grow a huge amount of respect for the level of convenience that Eloquent provides. You can do stuff like this in two seconds. But now you're starting to see that, yes, an API like this is just gorgeous, but behind the scenes we have to do quite a bit of work in order to make that possible. So yeah, if you're anything like me, you've grown a huge amount of respect for Eloquent.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟