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

Basic Post Comments Relation0:00

Today's hashtag HelpMeLaracast question is, Will you help me understand polymorphic relations and when I would use them? Okay, fair enough. So I've set up a little example here, and then we'll figure out what the problem is and why polymorphic relations might help us. Now, if we take a look at the app directory, I've set up the very typical relationship between a Post and Comments. So our class Post has a relationship to Comments, and that relationship is hasMany. So essentially, for Comments, you would say select * from comments

and that relationship is hasMany. So essentially, for comments, you would say select * from comments where post_id = 1, right? Very, very common stuff. Now, for the comments table, exact same thing. Now, if we want, we can add a post relationship here, and that would be a belongsTo in this case, right? A Comment belongs to a Post, and this is all very basic stuff. But now, imagine that your boss wants you to introduce the functionality to favorite a Post.

Adding Post Favorite UI0:51

But now, imagine that your boss wants you to introduce the functionality to favorite a Post. Now, if we switch over to Chrome, I've already written a bit of logic to display the post title, its description, and then a list of comments. So let's take a look. post/show, and if we come down, yeah, very simple. Show the post, show the body, and then for each post comment, then just spit it out within a list item. And then finally, for our setup, we have one route. When we try to visit a post, we're going to use route model binding.

And then finally, for our setup, we have one route. When we try to visit a Post, we're going to use route model binding to automatically fetch that, and then we're going to pass that post record to the show view, and this is what we get. Okay, great. So our boss wants us to add a little favorite icon to the top of each Post. Okay, let's get started on that. Post, show, and we'll set it up right here. Now, ultimately, it's going to be a form, but for now, let's just set it up as a button.

Now, ultimately, it's going to be a form, but for now, let's just set it up as a button. And I am pulling in Twitter Bootstrap here, so I can give it a class of button link so that it displays as a link, not a button. Finally, within here, we can reference a icon here. So we'll use the glyph icons that are included, and the one we want, I believe, is called heart. Okay, so if we go back to Chrome and give this a refresh, yeah, we can see it up here. And that's just because I've written a bit of CSS just to set the display to flex so that we can display these left to right.

Building Favorites Pivot2:33

and that sets it to red, but when you hover over it, it turns gray. Okay, so by default, it's going to be not favorited, right? But when you click on this icon, we want to favor the Post, and we want to remember it, and we want to set up a pivot table for that. But we're also curious about polymorphic relations, but we don't exactly know how to do that. Okay, well, one step at a time. So let's switch to the terminal, and I'm going to make a migration for the favorites table. So php artisan make:migration create_favorites_table, and the table we are creating is called favorites,

and I'm going to make a migration for the favorites table. So create favorites table, and the table we are creating is called favorites, or you can say php artisan make:model Favorite -m will create a migration file as well. Same thing. All right, so create favorites table, and our first stab at this will not be polymorphic. So we are assuming, well, our boss told us that we should be able to favorite posts, so it sounds like we need the id of the post, right? And it also sounds like we need to store the id of the user, right?

so it sounds like we need the ID of the Post, right? And it also sounds like we need to store the ID of the User, right? And what we might want to do at the bottom is set up a unique here. So we could either make both of these the primary key, or just make sure that the combination of post ID and user ID is unique. And I think that's good for a first pass. So let's go ahead and migrate our database, and we have a new favorites table. Okay, well, now imagine this. We're going to set up a new route here, and we'll say, route, when you post to, well, we could do this in a number of ways.

We're going to set up a new route here, and we'll say, route, when you post to, well, we could do this in a number of ways. We could say post, and then favorite, or we could elevate that to like a first class citizen. So post to favorites. It's just up to you. Let's use this syntax for now, though. So give us our post, and ideally, maybe we want to say something like this. I want to call a method called favorite, or we could do it in the inverse, user, favorite, the post. So the user might be the authenticated user.

User, favorite, the Post. So the User might be the authenticated User. So we could add the auth middleware here. But just note that I am going to fake that by just automatically logging in a User with an ID of one. That way we don't have to set up a registration form and all that annoying stuff. But anyways, when we hit this route, we're going to tell the authenticated User to favorite the given Post. But right now, a favorite method doesn't exist. Okay, so we come back to the User model. We set up a favorite. And now what is the relationship here?

We set up a favorite. And now what is the relationship here? Well, the relationship is a User can have many favorites. So we might say this belongs to many Post class. And then the name of the pivot table is favorites. Okay, so let's see that just to start. php artisan tinker, at User, give me the first one. I'm going to reference this favorites relationship, and we do get a collection, but they have none yet. Okay, so what if we wanted to say favorites, and we're going to add a new one? We want them to favorite the very first Post we find.

Okay, so what if we wanted to say favorites, and we're going to add a new one? We want them to favorite the very first Post we find. Okay, well now, if we do it again, sure enough, we get a collection of one Post. So this is a basic illustration of using a pivot table. You create a pivot table where you store the two IDs of the related tables. And then with Laravel, we can use a belongsToMany relationship to fetch all Posts favorited by the User, or attach new ones, or detach old ones. Or in Laravel 5.3, you could toggle the existence of one record. And also on that note, notice that if we try to attach a new one, it's going to fail because of that unique constraint that we added in the migration.

And also on that note, notice that if we try to attach a new one, it's going to fail because of that unique constraint that we added in the migration. So that'll keep you from having multiple records with the exact same combination. And this can happen if you don't protect yourself, like if the user clicks a button multiple times really, really fast. Well, if they come in that quickly, sometimes you do end up in that situation. It's a little tricky. But yeah, nonetheless, this will do the trick. So for favorite, you could say return $this->favorites. And yeah, we're basically reproducing this exact thing that we had before. So this favorites attach, and then we accept the post here, right?

And yeah, we're basically reproducing this exact thing that we had before. So this favorites attach, and then we accept the post here, right? And that looks good. So if we give it one more try, we'll say App\Favorite::truncate();, empty out that table. And then we'll say $user->favorites()->attach($firstPost);, the first post I find. And now once again, if we fetch the first User's favorites, we should get a collection of one item. And that'll do it. So now, yeah, the only remaining step is to determine if the user favorited this post, and we update the status of the heart icon. So yeah, we could do PostController@show.

Checking Favorite Efficiently7:03

and we update the status of the heart icon. So yeah, we could do post show. And then down here, we could simply say, well, if the post, and maybe our method will be wasFavorited, or how about isFavoritedBy the loggedInUser. If so, we want a class name of button favorited. Otherwise, a class name of button not favorited. But now, yeah, if we go back to Chrome, and we give this a refresh, it's going to fail, right? Because there is no isFavoritedBy method. So now, we come back, create our method isFavoritedBy the given user. And then we can simply say, give me the user's favorites, and tell me if it contains this post.

So now, we come back, create our method isFavoritedBy the given User. And then we can simply say, give me the User's favorites, and tell me if it contains this Post. So give me a collection of the User's favorites, which we may not want to do, but just first step. And then see if that collection contains this Post. So if I come back and give it a refresh, yes, in fact, it does work. However, one reason why this may not be advised is because if you think about it, behind the scenes, when you call favorites, you're fetching every single Post that the User has favorited. You're not fetching a collection of favorite records. You're fetching a collection of Posts. So if there are 500 Posts on my blog, and you favorite 400 of them, well, when I say User favorites,

You're fetching a collection of Posts. So if there are 500 Posts on my blog, and you favorite 400 of them, well, when I say User favorites, that's basically like saying select * from posts, where ID in, and then you provide an array of the 400 IDs that I liked. So that can be potentially a very costly SQL query. Another option is, and this is an important one, understand the difference between model relationship as a function, and then model relationship as a property. When you reference it as a property, you're getting a collection. When you reference it as a function, you're just getting it as a builder, so that you can actually finish creating your query.

When you reference it as a function, you're just getting it as a builder, so that you can actually finish creating your query. So now you could say userFavorites where the post_id equals this ID. And then we can see if anything exists for that query. So yeah, now we're not fetching all posts the user has liked. We're just performing a single query. Tell me if there exists any record in the posts table for this user where the post_id is this one. So now if I come back and give it a refresh, it should do the exact same thing, but it's a cleaner query to run. So now everything is great. You commit and you push it up to production and everything's going just swell, right?

Need Polymorphic Favorites9:23

So now everything is great. You commit and you push it up to production and everything's going just swell, right? But then your boss tells you, well, I also want to give users the ability to favorite comments as well. And now immediately you think to yourself, well, crap, because I have all these references to post_id. So how am I going to set this up? Because if I go back to my favorites table, it says post_id. But now we're working with comments. And already we're starting to think, well, if he wants us to favorite comments, then he's probably going to come back next month and want an ability for the user to favorite something else entirely. So we want this to be a bit more dynamic or a bit more polymorphic.

then he's probably going to come back next month and want an ability for the User to favorite something else entirely. So we want this to be a bit more dynamic or a bit more polymorphic. What if we did this instead? Yes, we're going to have a favorites table that contains a User. But I'm no longer going to make reference to any other model. I'm not going to assume a Post. I'm not going to assume a Comment. I'm just going to assume some kind of related model, maybe something like Favorable. So give me the favorable_id. So this would be the equivalent of post_id, but something more generic.

So give me the favorable ID. So this would be the equivalent of post ID, but something more generic. Next, though, if you can favorite a Post or a Comment, we need some way to designate which model the user is favoriting. Are they favoriting a Post or are they favoriting a Comment? So this is why we're going to have a second one for the favorable type. And essentially, this is going to translate to the path to the model. So like app\Post, or if you have Comment, then favorable type will be app\Comment. So a stringified version of the namespaced class. Okay, so now this is going to be more polymorphic. And we can see that the thing that makes each record unique is the combination of the User, the favorable ID, and the favorable type.

Okay, so now this is going to be more polymorphic. And we can see that the thing that makes each record unique is the combination of the user, the favorableId, and the favorableType. So I'm going to make sure that I add this in now. favorableId and favorableType. Okay, so let's say php artisan migrate:rollback. And then php artisan migrate to run it again. And next, well, let's do this. Let's go into our Post model. We're starting to realize that we may need to add favorable, quote-unquote, functionalities to the Post class, but then also to the Comment class, and then also to any new component that our boss wants us to make favorable.

Favorable Trait with morphMany11:23

We're starting to realize that we may need to add favorable, quote-unquote, functionalities to the Post class, but then also to the Comment class, and then also to any new component that our boss wants us to make favorable. So maybe we can make a trait for this, like Favorable. And then this, yeah, this would be extracted to that trait. Okay, interesting. So let's create that, Favorable. It's a trait. And then I'll set the namespace. So now let's think, what sort of things would we need to do with a Post? Let's just write it out.

So now let's think, what sort of things would we need to do with a Post? Let's just write it out. Well, it would be useful to find all favorite records for this post. That would give us a collection of favorite models specifically, and that can be useful. You could always say post->favorites()->pluck('id'). You know, you get the idea. I also want to know if the post was favorited by a given user. And also, it could be useful to do things like this, Post::where('favorited_by', $user)->get(), and then give me the results. And this is useful. Like imagine the URI is /user/1/posts or something like that, or /post/favorites, whatever you want to do.

And this is useful. Like imagine the URI is /user/{id}/posts or /post/{id}/favorites, whatever you want to do. So we want to fetch all Posts that have been favorited by the User. So maybe we could add a query scope called scopeWhereFavoritedBy, and that will basically limit all results to only those that have been favorited by the current User. All right, so let's get started. I'm going to bring these up here just as a heads up for what we need to implement. This first one can leave and come in here. Now already I know we'll have to rewrite that, so we'll leave it like that. But there will be a favorites relationship, and there will be a scopeWhereFavoritedBy query scope. So I have to start that with scope, right?

But there will be a favorites relationship, and there will be a whereFavoritedBy query scope. So I have to start that with scope, right? And that will accept the $query and then the $user. Okay, let's get started. Favorites. So we want to get a collection of all favorite records for this post. Okay, the relationship we want, and there's a number of polymorphic specific Eloquent relationships, especially when you get to like many to many polymorphic relations, it gets kind of tricky. So if it's kind of confusing to you, you know what? I've been doing this for years, and it's still a little tricky, and you're not going to use them that much. You may find that you mostly end up using the more simple ones, like morphMany or morphTo.

I've been doing this for years, and it's still a little tricky, and you're not going to use them that much. You may find that you mostly end up using the more simple ones, like morphMany or morphTo. Now, if you're trying to figure out what morphMany is, just think of it like this, hasMany. And that's really easy, right? A Post has many Comments. So when you change it to morph, just think of it as a polymorphic hasMany. It's a hasMany where you don't know the foreign key. That's polymorphic. So we use a different relationship there. I'm going to reference my class here, our favorite model.

So we use a different relationship there. I'm going to reference my class here, our favorite model. Remember, that's essentially our pivot table. Next, though, if we click through, it wants to know the name to look up. And that's basically the convention we used here. So we called it favorable_id and favorable_type. So I will include that here so it knows which prefix to use. Okay, so what do we have here? We have a Post class that uses the Favorable trait. The Favorable trait offers three methods right now.

We have a Post class that uses the Favorable trait. The Favorable trait offers three methods right now. The one we've implemented is favorites. So if I say php artisan tinker, give me the first post, and I want to know any collection of favorites. And it should be empty, right, because we remigrated everything. Why don't we manually build one up? We'll say forceCreate. The user_id will be one. The favorable_type will be a Post. And the favorable_id will be the post with an id of one.

The favorable type will be a Post. And the favorable ID will be the post with an ID of 1. Okay, so we've just manually inserted a new record. That means if we run our query again, we should see exactly 1, and we do. This Post has exactly one favorite. So now, yeah, like I said, you could pluck the favorable ID, and that'll give you a collection of all IDs that have been favorited. And then, of course, you can run all if you just want to grab an array of those. Okay, so lots of flexibility there. Next, was the Post favorited by this User?

Okay, so lots of flexibility there. Next, was the Post favorited by this User? Well, now we could just say return $this->favorites, but I do want to add a restriction, right? So give me any favorites where the user_id is equal to the $user you gave me. And once again, just tell me if anything exists there. So a very, very simple query. Okay, so boot up php artisan tinker once again. And we'll say $app->post first is favorited by the first User. And it is.

And we'll say App\Post first is favorited by the first User. And it is. However, if we build up a second User. All right, this User has an ID of two. And we'll say is favorited by auth()->user()->find(2). That should get false. Okay, so now that method works just fine as well. Finally, we want to limit all posts which have been favorited by the current User. So once again, I want to say Post::whereFavoritedBy(), or you could always just say Post::favoritedBy().

So once again, I want to say Post where favorited by, or you could always just say Post favorited by. And in fact, why don't we do that? Anyways, give me all Posts favorited by this User. Useful, right? So how can we do this? This might actually be a new Eloquent relationship to you. So we know that we can use has, right? So you could say Post that has Comments, give me the results. So return to me any Posts that have at least one Comment.

So you could say Post that has Comments, give me the results. So return to me any Posts that have at least one Comment. However, what if you want to further restrict this? So what if you wanted to say, I want Posts that have Comments, but specifically only the Comments where the userId for the Comment is John's, or something like that? Well, it sounds like we want to add a where condition to our has scope. Okay, we can do that, like this. So Post, in this case, where has favorites, referencing our relationship here. So give me the Post that has favorites,

So Post, in this case, where has favorites, referencing our relationship here. So give me the Post that has favorites, but specifically, I only care if the userId for the favorite. So remember, right here, we're going into the create favorites table. We're just checking to say, give me all of the favorite posts, but just as long as the userId for that row or for that record is equal to this user. So query where the userId is the userId. And because of php, we have to do this stupid use thing here. Hate it. But anyways, does this all make sense?

Hate it. But anyways, does this all make sense? Let's try it. php artisan tinker. And we'll say app Host that have been favorited by the first User. And give me the results. Okay, and we get one in this case. But even if I create more, so app Host, you know, give me three more here. Okay, let's run it again. So app Host favorited by the User should still be one.

Okay, let's run it again. So app Host favorited by the User should still be one. But if we fetch all Posts, we should get three. Okay? So this is really useful, and it can be applied to any model. That means our Post model is very clean. We're simply referencing a trait. The Favorable trait is pretty clean itself, because there's no reference to a Post model here. This can apply to anything.

because there's no reference to a Post model here. This can apply to anything. It's polymorphic. So if we go back to post show, we could say post is favorited by. Yep, that's all still going to work. So we give it a refresh, and we still get red. And in this case, red is good. We still have the same end result, but now we can apply the exact same thing to the comments, like this. We're going to go to the Comment model, and the Comment model,

now we can apply the exact same thing to the comments, like this. We're going to go to the Comment model, and the Comment model, according to our boss, should be favorable as well. So we reference that trait. That means instantly Comment inherits all of the methods here. Let's try it out. php artisan tinker once again. Not sure if I have any comments. Yep, we do. So we'll say app('App\Models\Comment'), give me the first one.

Yep, we do. So we'll say app(Comment::class), give me the first one. And to keep it clean, let's save that real quick. Okay, so we'll say commentFavorites, and we have not set up a method for this just yet. So we'll have to do it the long way. But we could say either create or save, and then just pass in a new Favorite model here. So we'll do this, $favorite equals new Favorite(); And what would we need to set up here?

So we'll do this, $favorite = new App\Favorite. And what would we need to set up here? The userId, right? So that would be, let's just say, the User with an ID of one. Okay, so now I could say comment->favorites->save our new $favorite. And there we go. And of course, behind the scenes, it's automatically going to set the ID of the comment and the type of the comment. And once again, that's because we're using Laravel's polymorphic specific relationships.

And once again, that's because we're using Laravel's polymorphic specific relationships. So now, think about it. If we fetch all favorites, notice that it didn't matter that we had a Post or that we had a Comment. This table, it doesn't care. It's irrelevant in this case. It just needs to know what the type is and what the ID is and what the User is. So this is good. It can now be as dynamic and as flexible as we ever need it to be.

So this is good. It can now be as dynamic and as flexible as we ever need it to be. Why don't we come down and add one more method called favorite, where we simply want to say $favorites, save. And then we could say a new Favorite where the userID is equal to, and in this case, we could say we could pass it in or we can just reference the authenticatedUser. And really quick, let's go up here and make sure that $user is fillable. Okay, so now, yeah, we just have a helpful wrapper around this confusing bit of code.

Okay, so now, yeah, we just have a helpful wrapper around this confusing bit of code. So we could say once again, php artisan tinker, Comment::find(3); Oops, I'm sorry. Give me a Comment with an ID of 3, and then we'll say favorite. We forgot to reference. Let's do this. Okay, one more time. And there we go. So now we can fetch all favorites, and we do get one in this case.

And there we go. So now we can fetch all favorites, and we do get one in this case. Useful. And of course, you can do the opposite. You could do unfavorite or toggleFavorite if you want. The only remaining step now is to display this in the comment section as well. So we return to postShow, and then we'll have this down here. Let's, where do we want to put this? At the top. Finally, we would just say comment is favorited by the user.

At the top. Finally, we would just say Comment is favorited by the User. And let's see how that works. There we go. The User has favorited these two. And of course, in real life, yeah, what you could do, rather than just having a button here that does nothing, you can maybe set up a form that submits a POST request to POST slash the Post ID. You know, once again, whatever convention you want to use here. And then this guy could go in here.

You know, once again, whatever convention you want to use here. And then this guy could go in here. Come back, refresh. And now that's at least a button that you can submit. I'm going to leave this to you because we're running a little bit high. But yeah, you would set up an endpoint in your routes file. So listen for a POST request to this endpoint. And then you're going to have your route do something like POST favorite, like so. And once again, don't forget what you can do. I'm sorry, in the Favoritable model, we are assuming the authenticated User here.

And once again, don't forget what you can do. I'm sorry, in the Favoritable model, we are assuming the authenticated user here. But you could accept the user. Or you could add a separate method to the User model so that the user can say userFavorite. There's so many different ways to do this. Or let this be null, and then you would do something like userId or get the authenticated user and make it more flexible for testing as well. Lots of ways to do this. But now think about it.

Lots of ways to do this. But now think about it. Polymorphic relations, which is just a fancy word. I don't even like it. It's just a term that should not be that confusing. But because we created our database table in this way, we can now assign the favorable trait to any model that we create. And then further, if you want, you could even extract this to your own custom package that you pull in for any of your new projects. So that way, later if our boss introduces some kind of videos section to your site,

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