Inverse Relationship Basics0:00
Now, before we move on to many-to-many relationships, give me just a moment to discuss the inverse of the first two here. Let's go to Post. Yes, a Post can have many of, how about, Comment. So if I want to grab the comments, we would represent that like so. Post has many Comment. Very simple, very easy to understand, right? But now, what about the inverse? Yes, a Post has many Comments, but what if you have a Comment here, and I have one set up already.
Yes, a Post has many Comments, but what if you have a Comment here, and I have one set up already. Well, if I have a Comment and I want to fetch the Post associated with it, we would do a belongsTo relationship, like so. And that makes sense as well. Is it true that a Post can have many Comments? Yeah, that's true. Now is it true that a Comment can have many Posts? No, that's not true. Would it make more sense that a Comment belongs to a given Post, or would this Comment make
No, that's not true. Would it make more sense that a Comment belongs to a given Post, or would this Comment make sense outside the context of that Post? No, this Comment is owned by the Post, which means we can say belongsTo. Now this makes sense for hasMany relationships, but it's also true for hasOne relationships. So here's what we had from a couple episodes ago. Does a User have one Profile? Yes, absolutely. Now let's go in reverse. If I have a Profile and I want to fetch the User, well, does the Profile belong to the
Now let's go in reverse. If I have a Profile and I want to fetch the User, well, does the Profile belong to the User? Or in other words, does the User have ownership of this Profile? Yeah, absolutely. The Profile belongs to the User. And this is why we say one-to-one and one-to-many. Let's do it real quick. Does the User have one Profile? Yeah, work it out in your head.
Does the User have one Profile? Yeah, work it out in your head. Would the User have multiple Profiles? No, they have one Profile. All right. Now does that Profile belong to the User? Could it belong to anyone else other than that User? No, it is linked to the User. Perfect. Next, does a Post have many Comments?
Perfect. Next, does a Post have many Comments? Well, let's do it in two ways. Would a Post have one Comment? No, it could have any number of Comments. It could have many Comments. Now let's do the reverse. Does a Comment, well, could a Comment have many Posts or be associated with many Posts? No, not unless you're spamming the site. The Comment belongs to a single Post.
Many-to-Many Concept2:23
No, not unless you're spamming the site. The comment belongs to a single Post. But now what about when we get into more murky territory, like this? Let's go back to Post. Let's imagine that a Post can be associated with tags. All right. Well, your first instinct probably is, well, does a Post have many tags? Yeah, I mean, I think that makes sense, right? So we try this out. But now I've already behind the scenes whipped up a Tag model.
So we try this out. But now I've already behind the scenes whipped up a Tag model. Let's do the inverse. I'm going to grab the Post associated with that Tag. But already I can't figure out, should it be Post or Posts? So it sounds like we need to really dig in and figure out the relationship here. Let's say this Post could have Tags, any number of Tags, like personal, family, vacation. And then each of those Tags, you know, if I had a blog, I could filter down all the Posts to only my vacation Posts or only my family-related Posts. All right.
posts to only my vacation posts or only my family-related posts. All right. So it does seem like this Post can have many Tags. But now let's go in reverse, this one PersonalTag. Does that belong to this given Post? Or does this given Post have ownership over the PersonalTag? No, that's not right. Any number of Posts could be assigned to this Tag, and this Tag could be associated with any number of Posts in reverse. So from that point of view, this would not make sense.
any number of Posts in reverse. So from that point of view, this would not make sense. We do not have a belongsTo relationship. This personal tag, let's say we have that, it doesn't belong to that Post, it's associated with that Post instead. So this is where we come into many-to-many relationships. A Post can have many tags associated with it, and a tag can have many Posts associated with it. We represent that in Laravel using the belongsToMany relationship. So here I'm on the Tag model.
We represent that in Laravel using the belongsToMany relationship. So here I'm on the Tag model. If I want to fetch all of the Posts associated with this given Tag, so if this given Tag that we have, this instance, is the vacation Tag, well, if I call this method, let's say vacation Tag, if I call this, that should give me all of the Posts that have been assigned the vacation Tag. Okay. And in reverse, let's grab that. We'll switch back, and we want a belongsToMany relationship there as well on Tag. Okay.
Pivot Table Requirements4:46
We'll switch back, and we want a belongsToMany relationship there as well on tag. Okay. Now, how do we represent this in the database? Because if we give this a shot, let's boot up php artisan tinker, we'll find whatever first post we have, and I say give me tags, it's just going to blow up. Because at the moment, we don't have a tags table yet. And also, notice it's trying to read from a post_tag table. So it sounds like we need three different tables set up in order to make this functionality work. You need your post table.
work. You need your post table. This is your blog. Every post will be added to this table. Next, you're going to need a tags table. This will be a single location where you register all tags that are available, family, personal work. You know, you can add as many here as makes sense. This should be fairly common to you at this point. So we have the post table, the tags table.
This should be fairly common to you at this point. So we have the post table, the tags table. The final table we need is what we call a pivot table. It serves as the connecting tissue between two different tables. And generally, the convention will be the singular form of both of these tables in alphabetical order. So that would be post_tag. And let's just see. If we go back to that query from earlier, that's exactly. Again, we can override this default if we want.
If we go back to that query from earlier, that's exactly. Again, we can override this default if we want. But as a convention, it will use singular form of both tables in alphabetical order. So it sounds like we have some work to do. So we have a post table. We have a tags model. But we don't have any tags table. And we don't have the pivot table either. Let's do those as two separate migrations. So make me a migration called create_tags_table.
Let's do those as two separate migrations. So make me a migration called create_tags_table. And we'll knock that out real quick. Now to start, I'm just going to say a table has a string field for the name. This is fine. I'm going to stick with that. So if I migrate my database, switch over to SQL Pro, give this a refresh, we now have a post table. We have a tags table that's empty. Let's add some real quick.
We have a tags table that's empty. Let's add some real quick. Personal. How about family? We'll stop right there. All right. Finally, we need a pivot table, the connecting tissue. And this will be the table that says the Post with an ID of 2 is associated with the tag with an ID of 1. So you'll see a pivot table consists of two different columns.
with an ID of 1. So you'll see a pivot table consists of two different columns. Let's do that now. php artisan make:migration create_post_tag_table. And by the way, even though this is the convention, often I will override that. And I'll show you how soon. All right. So let's go to that now. create_post_tag_table. And yes, among other things, we will need to set up a unsignedInteger for the post.
Create post_tag table. And yes, among other things, we will need to set up a unsignedInteger for the post_id. And then another one for the tag_id. And again, this is so we can say, all right, well, the post with an ID of 1 is associated with the tag with an ID of 1. Or additionally, the post with an ID of 1 is also associated with the tag with an ID of 3. We can have as many records as we need to here. Now, you can tweak this however you want.
We can have as many records as we need to here. Now, you can tweak this however you want. Like if you need to add an index to both of those, you can. If you want to set the primary key to be a compound, actually, we'll talk about that in just a minute. Leave it like this. And then once again, migrate our database. OK. So let's talk about this. We now have posts, tags, and post_tag.
Testing Relations in Tinker8:11
So let's talk about this. We now have Post, Tag, and PostTag. So again, if we were to do it manually, we could say the Post with an ID of 1 is associated with the Tag with an ID of 2. How about that? So we would represent that like this. OK. Let's see if we can fetch that now. php artisan tinker.
php artisan tinker. Give me the Post with an ID of 1. And now give me all of the tags associated with that Post. And instantly, I get a collection here, in this case, family. Now let's say, in addition to family, well, this Post is also personal in nature. OK. So let's set up another one here. The Post with an ID of 1 also has a tag with an ID of 1. OK.
The post with an ID of 1 also has a tag with an ID of 1. OK. So now if I exit out and do it again, we should now have a collection of two different items, which means if we were to pluck the name for this given post, we have family and personal tags. Let's now go in reverse. Let's grab the first tag, whatever that happens to be in the database, personal. And now we're going to say, give me all posts that are associated with that tag. Ah, in this case, only the one. Let's change that, though.
Ah, in this case, only the one. Let's change that, though. Let's say the Post with an ID of 2 is associated with the Tag with an ID of 1. All right. Post with an ID of 2 has Tag with an ID of 1. And now, once again, let's grab our Tag, get all Posts associated with that Tag, and we now get a collection of two items. Now of course, in real life, you're going to be doing this programmatically. So let's review that. I'm going to clear this out.
So let's review that. I'm going to clear this out. If we once again fetch our Post, I could say post tags, and we're going to attach a new one here. Now, we can do this in a couple of ways, but to begin, let's just say the tag with an ID of 2. OK. So now if we come back and give this a refresh, we can see the Post we found, so that is this guy right here, we added the tag with an ID of 2, and we associated with it, as you see there.
guy right here, we added the tag with an ID of 2, and we associated with it, as you see there. Let's do it in reverse. All right. The tag with an ID of 1, well this time we're going to say tag posts attach, and this time we would give it the post ID to associate with that tag. So let's say the post with an ID of 2. Let's give that a refresh, and now we've done it in reverse. Now you'll notice the timestamps here are null. We'll fix that in just a moment.
Now you'll notice the timestamps here are null. We'll fix that in just a moment. But until then, we have our post. Now give me the tags that we programmatically added, and we once again get family. Now what if we wanted to remove that? Well, in reverse, I could say post tags detach the tag with an ID of 2, and if we give that a run, we will now strip it from that table. Now let's do it one more time. If I were to say attach, we could even give it an array, 1 and 2. So now attach those two tags with the given post, and we will have two instances there.
If I were to say attach, we could even give it an array, 1 and 2. So now attach those two tags with the given post, and we will have two instances there. Or we could add an existing tag. So let's say give me the first tag in the database, and now I could say post tags attach that tag instance. And once again, we will now have the personal tag. Okay. It's starting to make sense, right? It seems clear at this point. A post can have many tags, but any single tag can be associated with many posts.
Timestamps, Keys, Constraints11:41
It seems clear at this point. A Post can have many Tags, but any single Tag can be associated with many Posts. We have a many-to-many relationship. So finally, what about the timestamps here? Well, for any given pivot table, you may or may not need them. This is where you need to ask yourself, is this entry here something that I might want to record timestamps for? It's up to you. When in doubt, I often say keep it, but it's up to you. Sometimes it just won't be necessary.
When in doubt, I often say keep it, but it's up to you. Sometimes it just won't be necessary. Nonetheless, if you want to add that on, you would say it belongs to many with timestamps. So you have to opt in, and we would do the same thing in reverse. Okay. So let's give it one more shot to prove to you this works. Now notice whenever I make a change, I exit out of tinker and then I boot it up. We'll do it this direction. Let's see. I'm just filtering through what we had before.
Let's see. I'm just filtering through what we had before. All right. Tag attach the Post with an ID of 2. Okay. So if we refresh, now you will see the timestamps have been populated. Now one thing to consider. If you're not careful, like what if a User clicks a button twice, and behind the scenes it will run the same command? It will basically run this two different times.
We have a couple of choices. One option would be to set your primary key to be a composite. So it could be post_id and tag_id. These two together form the primary key. Okay. Let's give that one a shot. So if I migrate, roll back, and then re-migrate. Okay. Let's give it a shot now. So find a tag.
Let's give it a shot now. So find a tag. There we go. Try to add the association. All right. Let's take a look. Refresh. Okay. Now we're back to that one example. But if we try to do it again, this time an exception will be thrown.
Now we're back to that one example. But if we try to do it again, this time an exception will be thrown. Because it's saying, hey, we already have an entry there, so what are you trying to do here? And you'll see that if we were to try to do it manually as well. If we save that, it's going to blow up. Now one or two final quick things, and then we'll call it a day. You might want to also set up some foreign key constraints, if you wish. You could say table foreign, and let's say, well, the post table. So in this case, the post_id, well, what does that refer to?
You could say table foreign, and let's say, well, the post table. So in this case, the post ID, well, what does that refer to? It references the ID column on the post table, right? And then subsequently, the tag ID references the ID column on the tags table. Now what exactly should we do here if the post it's associated with is deleted? Well, on that case, why don't you cascade down and delete this record in the pivot table as well? Let's do the same thing here. Okay. So this sets up the foreign key.
Okay. So this sets up the foreign key. It also sets up the index in the process. Why don't we give this one a shot now? Pull back the last migration, and then rerun the migration. And then if we boot up php artisan tinker, let's find a tag, and then associate it with a given post. And if we now switch back, we have this record here. And you'll see in SQL Pro, we have this little arrow next to each one. That's the result of the constraint.
delete this record as well. Let's give it a shot. Delete this, cascade down, delete the associated record in the pivot table, and you'll see this is now empty once again. And again, you're getting that specifically because of this logic here. Now final little note here. We are using the default table name. So in this case, post_tag, sometimes you will want to rename that to something a little more appropriate for your domain. So if you have a Post and a Tag, I don't know, I think this is fine.
Query Debugging with Debugbar15:58
more appropriate for your domain. So if you have a Post and a Tag, I don't know, I think this is fine. But maybe you want relationships or something like that. If you decide on that, just make sure that you add it as the second argument here. Or assignments, whatever you want. Now when the SQL query is executed, it will use that table name instead of post_tags. So to finish up, let's review the SQL query that's happening behind the scenes, and this time I'm going to show you a third way to monitor the queries. So you've already learned about DB::listen, right? You've learned about DB::enableQueryLog, and then subsequently DB::getQueryLog.
So you've already learned about db::listen, right? You've learned about db::enableQueryLog, and then subsequently db::getQueryLog. But if you want, you can also pull in a dedicated package. Laravel Debugbar does a really good job of this. So let's give it a shot. We'll go to installation here. I will give that a run. Now because I'm on a recent version of Laravel, the package should automatically register itself with the framework. All right, so let's try it out.
itself with the framework. All right, so let's try it out. I'm going to open up my routes file, and we'll just do some random queries here. We'll grab some User, the first one, and let's have that User create a Post. So we already know from a couple episodes ago that we have this relationship here. So and actually, in fact, this was an example, we didn't actually call it person_id. Anyways, I could say $user->posts->create([ 'title' => 'foobar', 'body' => 'lorem ipsum' ]); All right, that'll give us a new Post. Next, we'll save that Post, and then we'll say $post->tags->attach();
All right, that'll give us a new Post. Next, we'll save that Post, and then we'll say post tags attach. And let's see, do we have any tags in the system? Yeah, one. We'll just hard code that there. And I think that should be fine. So we should have one query to fetch the user, another query to insert a new Post into the database, and a third query to associate the tag with an ID of one with that Post. And then finally, we show a generic welcome page. Now if we give this a run, it is blowing up due to a mass assignment exception.
And then finally, we show a generic welcome page. Now if we give this a run, it is blowing up due to a mass assignment exception. And that's because I am mass assigning the columns here. We have two options. One, I will often set this to an empty array. As long as you know what you're doing, you should be fine. Okay, so if we come back, give it a refresh, everything's working, and we now have this little debug bar here. Very useful. Let's review the queries.
Very useful. Let's review the queries. And we can see, once again, we have three queries. Give me all of the Users, but limit it to one. So just give me the first User. Then we insert it into the post table. And then here's the new one from this lesson. This is where we added to our pivot table. Insert into post_tag, the post_id, the tag_id, and then once again, Laravel and Eloquent are dynamically going to fetch those values based upon the associated models.
Insert into post tag, the postId, the tagId, and then once again, Laravel and Eloquent are dynamically going to fetch those values based upon the associated models. Pretty cool stuff. So yeah, a bit more bulky than basic hasOne and hasMany relationships. But nonetheless, you're going to find these to be enormously beneficial.
