This episode is for members only

Sign up to access "Winging A Laravel Comment System" right now.

Get started
Already a member? Sign in to continue
Playing
08. Posting a comment

Transcript

00:00
Okay, we're now going to look at posting a comment. Now, one problem that we have at the moment with the system that we've built up is there's no method to authenticate here.
00:09
We don't have the ability to create an account, log in like we would with a standard starter kit. That's not a problem. We can fake this for now.
00:17
We'll come back to giving the ability to log in a little bit later, particularly when we want to manually test this. And of course, if you want to follow along
00:25
and learn about that as well, we can. But let's just focus on getting some user signed in and just the ability to create a comment and really importantly,
00:35
on whatever resource we're working with. So when I create a comment on this page, it's going to create it for this article. So let's just do this really basically.
00:43
So we've got our list of comments. That's all good. Let's go ahead and just make sure that we've got a user in the database
00:48
that sort of looks like me. So I'm actually going to go over and let's run phpr tinker. And let's use the user factory to create our user.
01:00
And I'm going to make this sort of more personal to me. So let's set the name here to Alex. Let's set the email here to alex.codecourse.com. And let's set the password here to,
01:15
we'll use the bcrypt helper to hash password. Just so when I do come to sign in, that is password. I think it's the default anyway with Laravel factories, but we'll go ahead and create that out.
01:27
Okay, so we've got a user in here that should be ID of two now. So what I can do is within my system, and I tend to do this when I'm just sort of messing around,
01:35
is just at the top of my route or in a service provider, it doesn't really matter. I'm going to put it in my route, so I remember to get rid of it.
01:42
We're going to say something like auth login using ID, and we're just going to pass the ID in. So now when we come over to our application and we give this a refresh,
01:51
technically I'm authenticated now within this application. So just to sort of show this, we won't go too deep into this because we'll come back to this later.
02:01
Let's go and create out a sort of navigation area at the top here, and then we'll have a div here. Could technically create that as a section, but let's just keep it super simple for now.
02:14
We'll create a nav out, and let's say flex item center justify between, then we're going to have a left section here, and we're going to have a right section here.
02:26
Great. And then this content section should be added here. Yeah, let's set a margin top of 12 on there. Okay, yeah.
02:34
So on the right hand side here, what I want is if I'm signed in, so let's use the auth directive here. I'm just going to show my name.
02:44
So that's going to be, we'll just output that as text within a div, and let's say auth user name. So I know that I'm signed in, which is great.
02:56
And then we can have like a log out link next to that for a little bit later when we get to building that page out, and we'll just keep that really, really simple.
03:04
For now, I'll just create this as an anchor. Again, what we'll do is say flex item center, let's say SpaceX 2. We can even make this an unordered list.
03:17
That probably makes a little bit more sense. And there we go. So let's space that out a little bit more. And yeah, that will do for now.
03:29
Okay, so we know that we're logged in as this user. Let's get rid of this left section here. So now when we come to post a comment, we know it should be from us.
03:39
So what are we going to do here? Well, remember wherever we put this comments.blade.php component, this is just all of our comment functionality.
03:49
The point is we want to just be able to drop it in. So what we're going to do here is just underneath or just above the actual comments, this is going to be the form
04:00
where we want to go ahead and post this. So let's just start bringing in what we need here. So the name here is going to be body. Let's set the ID to body,
04:07
although you could technically change that just in case there's any other element on the page that has body, you might have comments with another form.
04:14
And let's go ahead and set the rows for this to six. And let's add some styles here. So let's say width full. Let's say resize none
04:25
because we don't want that to be resizable. And let's just check this out down here. Yep. So the other thing we don't have pulled in at the moment,
04:34
I don't think is tailwind forms. Let's just go over to app.css and see what we've got in here. Yeah.
04:42
If we head over to the plugin GitHub page here, we can go ahead and install this. And this just gives us a nice reset now form. So let's do an npm install on that.
04:51
And then let's use tailwind forms plugin just inside of here underneath the import. And do we need anything else? No, that's just for CSS tailwind version three.
05:03
And I think we're good. So that's going to give us a nice reset. And you can see that already we've got a outline or a border on this text area now.
05:11
So what I'm going to do is just reduce this down a little bit. I'm going to add a placeholder in here. What would you like to say, for example?
05:20
And we'll keep this more accessible friendly. So we're going to add a label for this. So let's say comment body. And let's say body.
05:32
In fact, let's, yeah, let's say comment underscore body so it doesn't interfere with anything else on the page. That kind of makes sense. And we're going to set a class of this SR only.
05:42
So it's visible to screen readers, but not to us. And there we go. And then for the form, let's just add a margin top on there.
05:53
Let's bum that down a little bit. I think that's fine. Okay, so we can style this up by going ahead and setting this to rounded Excel, for example,
06:03
rounded normally. And for the border, let's say gray 500. Bum that down a little bit more. There we go.
06:15
I think that'll do. Okay, so underneath we need a submit button. Again, we're just creating this really, really roughly now. So it's not going to look great.
06:22
Let's create a button type here of submit. And let's say post comment. And there it is. Let's just give this some really basic styles.
06:34
I'm really not going to bother too much at the moment. So let's say border gray 300, BG gray 200. Let's set some padding on the X axis here, three.
06:47
Let's set some padding on the Y of 1.5. Let's set this to rounded. And that will do for now. So this now I'm going to set a slightly larger margin
06:58
on the top, just so we can see all the comments. And we'll also go over to the main comments component and we'll switch pagination back to a sensible amount. Okay, so now we've got the ability to enter a comment,
07:08
hit post comment. Obviously this doesn't go anywhere at the moment. At the moment, it's just going over to the page that we're currently on.
07:15
So we need to figure out where this form actually needs to go, first of all, so we can submit it properly. So let's say method post.
07:24
And for the action, this is now what we need to figure out. So the question is, do we create over in our web routes,
07:34
a route that handles comments for any type of thing? So this would be a single route, which would handle comments for articles, episodes, et cetera. Well, we could do that.
07:47
And I think we're probably going to end up doing that because if we think about it, there might be different actions that you want to take based on which thing has been commented on.
07:57
Or we could go ahead and pass over the type of thing we're working on in the model ID as we create the comment. Let's start out with that.
08:05
So I want to really make this as reusable as possible, but then later we could switch this up into two controllers. So let's go ahead and create out a controller in here. And we're going to call this comment store controller.
08:23
And should we put that in its own directory? No, let's just do them all in the root directory for now. So we've got comment store controller. And what we're going to try and do here,
08:33
we'll make this invocable, is we want to try and die dump in here a few things. So what do we need to pass down to this controller to know how to save this comment?
08:44
Well, we need the comment body. That's the most obvious thing. And that's really all that we really need to actually store the comments metadata.
08:53
We will already know the user. So we'll already know which user it is. We'll get that from the request that we get through in here. So that's pretty straightforward,
09:04
but we're going to need to know the commentable type. And we're going to need to know the commentable ID. Not necessarily so we can fill these indirectly, because if you think about it,
09:15
we can look this model up. So we can look up the model type and the ID using a query. Then we can access the comments relationship and then we can store the comment body.
09:25
We might end up reversing this, who knows? We're just sort of bringing this, but let's just try and get to this point. So let's go over to our comments here.
09:34
And what we need to include inside of this form for now is a hidden input. Now, when this is a hidden input, that doesn't mean that to say
09:42
that we can just leave this and not validate it. That's really important. We need to make sure we validate it. So let's go ahead and say input type of hidden.
09:51
Let's say commentable type, and we'll set the value to the model itself. And we know that we have that over in our main comments model here.
10:01
So we can say model. Let's just see what that does by dumping it out actually. So let's just, we can look at the source or we could just dump this directly in the form.
10:10
And yeah, so we've got the model there, but we need the type of model. Let's say get class and see if that gives us what we want. And okay, so that kind of does,
10:21
but remember we are storing this as a value in our morph map. So if we come over to our app service provider, we've got this here.
10:32
I wonder if there's a way over in this relation class to actually get this value. So let's have a look through the methods here and see if we can actually use this to get the key.
10:44
So let's have a look here and I don't think we can. Let's have another look through here. Get keys, get morph alias. Yes, get morph alias, perfect.
11:00
Let's use this. So this might be over-engineering it, but let's do it anyway. So over in here, let's say relation,
11:09
get morph alias for that model. Let's make sure that we just import this from the correct place. So relation is under illuminate database,
11:25
elephant relations, relation. So let's pull this in from there and that should pull that in at the top, which is absolutely fine.
11:31
We can deal with that. Okay, let's try that out. And we might just need to actually pass the class name in here.
11:42
There we go, perfect. So that is exactly what we need because then we can use this to determine what we actually look up.
11:49
What we don't wanna do, and the reason that I'm not going ahead and just passing through the full path to this is what we're technically doing then
11:56
is we're exposing the location of the models in our code. It's not the worst thing to do, but we wanna sort of keep this as simple as possible. And what we can now do is hopefully
12:07
using that relation class, we can reverse engineer this and look back up the model that we wanna use, and we can use the ID of the commentable ID
12:15
to figure this out. So the commentable type is this, and we're gonna have a commentable ID, which is gonna be the model ID.
12:23
So again, that might be a slug of the thing. If you prefer to look it up by the slug, that's absolutely fine, but I'm just gonna go ahead and say model ID.
12:33
Let's go ahead and check the source for this just to make sure that this looks good. And yeah, there we go. We've got article one.
12:40
All right, so let's figure out now how to look this up when we actually post it through to comment store controller. So let's die dump on request only
12:51
just to make sure we can get this stuff. Commentable type and commentable ID. And let's give that a refresh, post comment. And yeah, let's go over here and actually register this
13:09
and actually hook it up. Obviously we haven't done this at the moment. So this is gonna be our comment store controller. Name of this is gonna be comments.store.
13:24
And we could set up a middleware group now. Why not? So let's say root group. And in here, we're going to our closure
13:38
and put this in here. And for this group, let's go. And what do we do here? I don't use root groups that often.
13:51
What do we do when we define our root group? Let's just have a look. So we've got our attributes, then our roots. Okay, what I'm gonna do is set them to none
14:00
and then C because I think we can just do middleware at the end. And let's say off. Okay, we'll check this out.
14:08
We're gonna be writing tests for this anyway. Okay, so we've got that. Let's go over to our comments and let's update the action here,
14:15
which we now know is root and comments.store. We can do that properly, comments.store. And of course, we'll also include in, of course, our request forgery token
14:27
so that actually gets submitted through properly. Let's go back over and try this again. ABC post comment. And there, there we go.
14:34
So the question is now, how are we gonna say, well, I wanna look up the article with this ID, then fetch the comments for that, the relation, and then post this comment?
14:44
Well, what we can do here is hopefully use that reverse relationship to look this up. Now, we do need to validate here. So we're gonna sort of ignore that for now.
14:54
We also need to be aware of the fact that because we are posting this in a form, it also means, and we could change this up and have it in the URL.
15:06
It doesn't really matter how we post it. But in this case, we need to make sure that we check whether this model that's been given in this commentable type is actually a commentable thing.
15:18
So we'll figure that out as well. Okay, so over in our comment store controller, let's just get this working first. So let's work out where the model is.
15:25
How do we get this model? Well, we could just say article and then find by the ID, but we know it might not be an article. So let's experiment with the relation class again
15:34
and see if we can find this. So we know get morph alias will pick this up, but maybe get morph model. I've genuinely never used this before.
15:45
So I'm just gonna test this with article and see what happens. So let's give that a refresh. And yeah, great.
15:51
Sure enough, we can sort of reverse the thing that we did before and actually figure this out. So what do we wanna do here? Well, we wanna say model and assign that model.
16:04
We could do this all in one go. So we could say get morph model article, but remember this might return null, in which case we know it's not a morphable thing.
16:14
So let's just see what happens when it does fail. Let's start up on model just so we can check this and we just get null. Okay, great.
16:24
So in this case, let's just say request commentable type. Request commentable type. Let's abort this if the model is null. So we'll just say no model.
16:42
And we'll abort with a, what's probably the most appropriate status code here. We could say 403 forbidden or 404 not found. Let's say 404 not found.
16:54
So abort if 404. So now we can die down the model and we know that in our case, that's gonna be the article.
17:00
Now what we can do is we can say model find and we can find that by the commentable ID. Or more appropriately, we could say find or fail and that would trigger a 404
17:13
if that wasn't available either. So we can tweak these around later if we want to, but let's see. So let's say model in here,
17:21
reassign that and then let's die down the model down here. And what we should get is the actual model that we need to comment on itself. Great.
17:31
So let's just think about this. If we do have a morphable thing, that doesn't necessarily mean it's commentable. So again, let's say abort if,
17:42
and again, we can refactor this. If not, let's say model comments. Let's do 404 again. So die down model and there we go.
17:54
So basically this will just fail here if we don't have a comment relationship on this. So yeah, we should be all good. And comments will return a collection,
18:06
but it will return null if not. We'll check that out later with a test, but we should be good. Okay, so I don't really like the way this is looking,
18:14
but we can refactor this later. It's absolutely fine. So now we're actually gonna post a comment and we're gonna redirect back.
18:22
That's probably the easiest part. So now we can say model comments, because we are pretty confident that it has a comment relationship in there,
18:31
and we can go ahead and save our comment. So the comment that we're gonna save is gonna be a comment. And, or let's say,
18:40
over to our user model, the user has comments. So we could say auth user comments, make request, only body.
18:54
Will that work? So we make this out. We don't persist it in the database. Then we save it to the model's comments,
19:02
and then we return back. Let's try this out. Again, we're gonna write tests for this as well, but we'll have a look.
19:08
Okay, so what's going on here? Method not allowed. Oh yeah, it's fine. Let's go back to that article.
19:14
Okay, so let's say great article. And fingers crossed when we post this, it doesn't work. Okay, so field body does not have a default value. So why isn't that getting saved in here?
19:29
Let's just make sure we actually have a body in here. And it doesn't look like we do. Great.
19:40
Ah, what do we call that comment body? Yeah, because we don't want to, we don't want that to get confused with any of the elements on the page.
19:52
So we've specifically called it comment body. Okay, let's try it again. So let's say great article. That's it, post comment.
20:00
And I'm gonna come, yeah, great. So because we've called that comment body, we can't do request only. That's fine.
20:08
Let's say body request and comment body. We'll get there. And let's try this one more time. It should be good now.
20:17
So let's go back to that article. And oh yeah, so it did actually post, but if I just refresh that article again, we're redirected back and we need to scroll down,
20:30
but we'll fix this in just a second. So to fix that, we're gonna redirect back with fragment and the fragment is comment. So let's do this again.
20:40
So another great article, post comment. There we go, perfect. So we now have the ability to post a comment and we have the ability to post a comment on any model
20:52
because what we're doing is we're leaving this open. At the moment, I don't like how this looks. So we'll think about how we're gonna refactor this, but we're using that relation class to reverse engineer
21:04
which model we're using based on the commentable type that we're posting through. We are validating that to make sure that it's actually a more thing.
21:14
Then we're validating to make sure that this comments relationship works. In the next episode, we're gonna write some tests here because we're building quite a lot of functionality out here
21:22
and we're not actually writing tests for any of it. So let's jump over to the next part and just focus on writing some tests for the instances and the situations that we have here.

Episode summary

In this episode, we finally tackle posting a comment to our resource! The big thing we start with is authenticating a user — since we don't have real authentication set up yet, we just "fake" it by force-logging in a test user straight from the database. Later on, we'll do proper authentication, but this is enough to move things along.

From there, we walk through building the UI for leaving a new comment: a textarea, some simple styling (including adding Tailwind CSS's forms plugin for consistency), and a submit button. The form doesn't do anything just yet, but now we're ready to wire up the backend.

We discuss how to make the comments system flexible enough to attach comments to any model (not just articles), using morph maps and Laravel's relationship utilities. To keep it secure, we make sure to not expose any sensitive class names, and validate any posted data. In the controller, we retrieve the correct model and make sure it's actually commentable before saving the new comment.

Once comments can be posted, we fix up redirect behavior so after posting you're sent straight back to your comment in the list. It's a bit rough and ready at this stage, but it works for now — we'll refactor and beautify later.

At the end, we celebrate getting multi-model comments working, and tee up the next episode, where we'll dive into writing good tests for all this new functionality.

Episode discussion

No comments, yet. Be the first!