Lessons: 2Length: 13 minutes

Next lesson playing in 5 seconds

Cancel
  • Overview
  • Transcript

1.2 How It Works: Laravel Eloquent ORM

Eloquent relationship types are very powerful and provide an elegant interface to relational data. In this lesson, we’ll take a look at each of the basic relation types and learn how they work. I’ll show you how each relation translates into an SQL query, and I’ll explain how it all works behind the scenes.

Code Snippet

Here’s an example of a class that has a hasManyThrough relationship.

<?php
namespace App;

use Illuminate\Database\Eloquent\Model;

class UserRole extends Model
{
    protected $casts = ['user_role_id' => 'integer'];

    public function orders()
    {
        return $this->hasManyThrough(Order::class, User::class);
    }
}

Related Links

1.How It Works: Laravel Eloquent ORM
2 lessons, 13:10

1.1
Introduction
00:23

1.2
How It Works: Laravel Eloquent ORM
12:47


1.2 How It Works: Laravel Eloquent ORM

[SOUND] Hi, I'm Mathew Machuga and in this tuts+ coffee break video I'm gonna give you an introduction to the way Laravel eloquent ORM handles relations and give you a peek at how it works under the hood. Inside of this example Laravel app which you can get from their latest source files. I've added to set up script to get the app into a working state with generated data, I'm gonna run that now. Next let's briefly inspect the C data so you know what to expect. Here we have five tables all related in some way. Users, user roles, orders, products and order product. You can ignore a lot of the weird looking generator code below. It's just how I get data into the database while ensuring relations match up to other C data. I'm going to show you how to use elegant relations to access related data from these tables conveniently. Now let's open up our user class and see what we've got. These two lines are important to relations but I wanna point out why I've included casts. The database engine I'm using for demonstration is SQLite. By default everything from SQLite gets returned as a string even integers. As such I've specified that I want my foreign key to be cast into integer explicitly so that I'm confident I have an integer to work with when I use it or if eloquent uses it as a foreign key. It may not be necessary on some systems but I prefer to use caution with these things. Let's move on to our first relationship type belongs to. Role is a belongs to relationship meaning that a user belongs to a certain user role or that the user role foreign key is in our user table. In the method call, I have passed the user role class name since that's the class we want to relate to. And I've also passed a foreign key is the second argument. The reason you see it here and not in the other relationship below is because the name of my function does not match the name of the foreign key according to Laravel's conventions. So by default it wouldn't be able to find the proper foreign key in the situation. Let's take a look at the source code for this method to help explain it better. I'm gonna use ttags to jump right to the belongs to definition. On inspection the method signature can accept up to four parameters, but only the first one, the related class name, is necessary. The second parameter is where we set the foreign key to look for in the Users table as you just saw in our model. We'll ignore the other two parameters for now in the first block of code and jump right down to the second. We can see that if we do not provide a foreign key Laravel will take the relation name, convert it into Snake case and then append underscore ID. This is how I'm able to use role as the name of the relation so long as I can pass in the foreign key name. Speaking of the relation name, this first block uses a bit of magic to sort out the name of our relation on this class even if we don't provide it as a fourth argument. When not provided, it uses this odd function you may not have known of, debug backtrace. Define the name of the calling function itself, which is whatever our relationship function is defined as. How in the world does it do that? Let's open up Tynker and check it out. I'm going to create a function called foobar that is simply going to return the exact same code we saw in the relation. Now, we call foobar, we get an array of two elements which corresponds to the integer two be passed in as the second argument. Let's focus on the top element, the results contain three keys. The first key is file which is the file where this code is contained. In our case since were being evaluated through a ripple, it gets evaled. So, it gets us closer to the filename as it can. The second is line which corresponds to the line number that this function was declared on. Our shows one again because it was evolved but if we look at the second entry we can see line 79 which is the code that executed the eval. Finally, the last key is function and that's the name of the function that called debug backtrack in our case it's FUBAR, pretty neat. Eloquent uses PHPs native facilities to analyze the call stack and grab the function name of your relation even if you don't declare yourself. Let's try it out. I'm going to assign user to be the first user record. If we access it's role relation without parentheses it'll perform some extra magic that we'll look at later and return to us the related record which is admin of ID two. If we take user role with parenthesis we can see that the relation gets returned which is what we want right now. Before I show you the next thing, I wanna show you something helpful for when you're trying to understand what each relations doing or even for what any other eloquent queries doing. At the end of any relation or query before you fetch it with the first or get if you type two sequel on the end It will return the sequel that's gonna be used to make the request. Okay back on track. If I call associate on user role and pass in the first user role. We can see that we get back our user with the role relation updated to the new user role and the foreign key user role ID has also been updated. Now just to see what happens when we mess with the relation name, let's jump back into our editor and back to our user model, we'll pass null is the third argument so that is still ignored and then pass FUBAR as our relation name. Reloading Tynker will store off our user again. Check its current role which is still two, then we'll associate the user role two. And now when our record gets returned, we can see that the foreign key was indeed updated properly but our role has been assigned to the relation name foobar. We can access it by typing user foobar and it works just like any other relation but it wasn't actually what we wanted. You may think of a legitimate reason for this in your code like maybe method aliasing, but in general just leave the arguments you don't need blank to avoid unexpected behavior. Okay, I'll revert that change back and that'll wrap us up for the belongs to relationship. Now let's move on to our next relationship type, has many. In a has many relationship, our users table will not contain the foreign key for the relation which is orders in this case. Instead the order table will contain the user ID field so that a user may have zero to many orders but an order will always belong to one user. Defining a relation looks nearly identical to the definition above except instead of using belongs to, we use the has many methods. Let's open this up in Tynker to see what using it looks like. If we again store off a user and access our orders, we can see we get a collection of order objects returned. If we run two sequels on the relation method we can see the generated sequel that is used to fetch a relationship. Hopping back over to the editor, all you see tags again to dive into the definition of has many. And as you can see it's very low on magic inside of the method itself. Let's jump in to get foreign key and we can see that it uses a very similar kind of lookup where it looks up the name of the model, Snake cases it and then appends underscore ID to the end. If we jump back up and go down to get key name, it just uses our primary key. So it's pretty straightforward. If you wanna see more of what it does with these arguments, feel free to check out the has many relationship class. That kind of a deep dive is fun but a bit too involved for the screen cast. All right let me jump up to the top level here and now we can switch over to our order model for another relationship type, a Many to Many relationship. In this scenario we have an Orders table a Products table and an Order Product table which I've named singularly as per levels conventions. If you remember from the seed file. Neither our order model nor our product model will directly contain a foreign key to the other table. Or rather the foreign keys will both be kept in the order product table which will act as the pivot table between these two. This will allow for an order to have many products and for our product to be in many orders. Let's see what that looks like in Tynker again before we dive into the code. If we fetch the first order and access its products, it'll look nearly identical to our last dataset from a has many. This kind of abstraction is nice in that you don't need to care what kind of database relation your model has as a consumer of the adequate model. You simply access the relation products and you are assured that you receive a collection of products back whether it's a has many or many to many, as if it wasn't even connected to a database at all. Let's inspect with two sequel real quick to see how the generated sequel looks. And in this string we can see it doing the join through the pivot table to access the correct records. Eloquent does a few magical things to look this information up but it's largely just playing off what we've already seen. Let's take a look in the code. This time looking up a relation name is tucked away in a method called BitBelongsToManyCaller(). It's still using debug backtrace but this time it spins through the array until it finds the first function that doesn't match this method shich will be our model relation method name just as in belongs to. Hopping back out of here and moving into the next bits, we're grabbing the proper foreign keys for both our current model and the related model. And then if a table name was not provided by the second argument we'll call this method here joining table to look it up. And this method recreates the table name for us in the way I demonstrated earlier. It takes the class name for both the current and related models, Snake cases them, sorts them alphabetically. And then joins them together with an underscore since O comes before P in the alphabet. That's where table name is order product and that's about all there is to a many to many. Finally I have one last relationship type to show off for the screen cast and that is the has many through relation. It allows us to use an existing model to pass through in order to grab some data that the second model is related to. So model1 has a relation to model2, model2 has a relation to model3. And thus model one can reach through model two to link to model three. Our user role class has one of these set up already. Having orders on here lets me look up the orders placed by all users of a given user role. I first pass in the model that I want followed by the model I'm passing through as the second. Let's jump over to Tynker for an example. I'm going to the first user role and now let's check out its orders and now we can see a collection of many orders placed by many different users. Pretty cool, the sequel for this is pretty close to a many to many code overall. Let's check out the code for this method in the eloquent base class. This method takes five arguments total but three of them are foreign key names for each model, so it's pretty straightforward overall. As you remember from earlier, our foreign key is derived from the class name and get key name simply looks up the primary key for our table. For what is conceptually a complex relationship this method is actually very easy. Now while we're in here, the last thing I wanna address is how our relations work differently depending on if we include parentheses or don't include them. As you may be aware, PHP supports magic getters and setters or setters and getter that don't exist in methods but instead have dynamic code that will execute if a property on an object is not found. PHP's magic getter is the double underscore get method. Starting there we can see that it delegates off to the get attribute method. And jumping into that get attribute first checks to see if there is a custom excess-or that's been set for a given attribute or if the attribute exists in the attribute list. If so we get that specific attributes value and return it. Else we check to see if it's a class method and if so, we return null. If none of these cases are met then this final get relation value is invoked. So let's check that out. If the relationship has already been loaded into memory then Ineloquent will return the cache record or records. If not Ineloquent can see that you have defined a method by the name of that attribute you tried to access. It will invoke get relationship for method. Inside of there the relationship method is called and stored off to a temporary variable. If that variable is not of a type relation than eloquent will throw an exception. Otherwise it will set the relation value to be the result of invoking the relationship query. So it caches that value off to be used if we access the same property again later. And it returns to us the data set. So as it turns out most of eloquence magic is very mechanical and it's put into place to make it very easy for you to access dynamic data, access relationships and cache the values of those relationships so you don't waste round trips to the database. I hope this little primer on eloquent relationships has given you enough confidence to start using it and trusting as default behavior. We've seen how we don't need to conform to all of Eloquent's expectations. In that we can pass optional arguments to relationships in order to properly match table names or key names. We've also seen some of the nifty helper features the Eloquent has baked in for us. At this point I highly recommend experimenting with the Rebel once you're done with this video and encourage you to start trying out some of Eloquence other features in order to get the most out of this fantastic active record ORM.

Back to the top