مرور بهروزرسانی با درخواستهای PATCH0:00
All right, welcome back, everybody. Okay, so we have one more episode to complete our first Notes CRUD app, or resource, if you like. And then after that, we can move on to sessions and authentication, which should be a lot of fun. Okay, so let's dig in right now. If I switch to my browser, here's our Notes section. And yeah, we can create an example note, and that works fine. We can view all of them. We can view a specific note.
Planning Edit and Update0:27
We can view all of them. We can view a specific note. We can delete a note, but yeah, we can't yet edit and update a note. And that's what we will do in this episode. All right, so let's go into our browser, and yeah, let's do this. Maybe we can actually, if I switch back real quick, maybe we can reuse this form here. Okay, so this is the Create page. So I will go into Views, Notes, Create, and I'm going to copy and duplicate this, and we will call it edit.view. Okay, so now if I go into, actually, let's start at the route level.
Adding Edit Route0:59
we will call it edit.view. Okay, so now if I go into, actually, let's start at the route level. Let's say listen for, let's grab this one right here, listen for /note/edit, and that will load a controller called NotesController. All right, that's our next step, create the controller. So once again, let's take something we already have and duplicate it, and I will call it edit.php. And yeah, at least initially, to make sure this is working, I often do this. Let's just echo hello and see if we get anything in the browser. Okay, so let's give this, actually, let's go into a specific note and then update the
Let's just echo hello and see if we get anything in the browser. Okay, so let's give this, actually, let's go into a specific note and then update the URI to be note/edit. All right, and sure enough, we are hitting that controller. Okay, so let's switch back, and yeah, let's think about what we need to do. Maybe you want to load a view to edit an existing note. We've already created that view. The heading will be edit note, and yeah, let's just leave it like that, come back, and refresh the page. And there we go.
Creating Edit Link2:09
the page. And there we go. Okay, so now, of course, I need a link to edit that note. So maybe when we show it, yeah, possibly right down here, we will add an edit link. Okay, so let's go into views/notes/show, and here's where we delete the note. But you know what? We might even move that to the edit section. Let's hide the sidebar. But yeah, right here, we'd have something like an anchor tag that will go to note/edit, and we'll call it edit.
And then we'll make it rounded, and yeah, maybe a little less padding, and yeah, just something simple to get us going. Okay, so now if I click on this, we can edit the note. But yeah, really, this isn't right, of course, because I don't see the note here. So if I'm editing a specific note, I have to fetch the specific note, and to fetch a specific note, I need to know what note we are working with. So just like when we show a note, when I click on the edit link, we need to include the ID of the note that we wish to edit. So again, right now, we are passing that through as part of the query string. So we'll do something like this, noteId, and let's give that a shot.
So again, right now, we are passing that through as part of the query string. So we'll do something like this, noteId, and let's give that a shot. Come back, give it a refresh, and now when I click on the edit button, there we go. We are editing a Note, and specifically the note that has an ID of 39. All right, next step is to track that down. So we will go into our EditController, and yeah, actually, if you want, we can just steal some of the code we wrote in the show action. So stuff like, really, a lot of this. Because yeah, if you think about it, so much of your controller action logic revolves around finding things, validating things, authorizing things.
Because yeah, if you think about it, so much of your controller action logic revolves around finding things, validating things, authorizing things. So they will often be somewhat similar. So I'm going to paste all of that in, and notice my editor will automatically import those classes. But if you're in something like Visual Studio Code, you might have to do that manually. Okay, so let's see. We're going to track down the corresponding note. That should all be the same. If we couldn't find the note, then we abort with a 404.
That should all be the same. If we couldn't find the note, then we abort with a 404. We then authorize that the current user, which we're still hard coding for one more episode, we're authorizing that that user has access to view and edit this note. Because otherwise, if we didn't have this, then anyone could access that edit URI to tweak your note, which, of course, we don't want to allow. So we have to implement authorization at every level here. Okay, otherwise, if we did find the note, we can pass that through here. Okay, cool. So now if we go to the edit action, think about it.
Okay, cool. So now if we go to the edit action, think about it. Where we have the text area, you know, right here, I can replace this with the body of the note. So note body. All right, cross your fingers, come back, give it a refresh, and there we go. Okay, but now think about it. What if I change my mind and I want to cancel? Well, I probably need a cancel button right here. Okay, so let's go back to our editor.
Well, I probably need a cancel button right here. Okay, so let's go back to our editor. And let's see right down here. And you know what it looks like when we use that Tailwind layout. They already had some nice styling for a button. And again, I know this is a big mess, and you might think, why would you ever want to repeat this over and over? Don't forget, when you're actually building applications, there are ways to isolate these things. You could extract the button into its own component.
things. You could extract the button into its own component. You could create a dedicated CSS class like button where you apply all of these. There are many different ways to tackle these things. But again, utility classes and Tailwind just aren't part of this series. But we have plenty at Layercast if you want to learn more. So anyways, right up here, why don't we duplicate this, but swap it out to an anchor tag. And then I'll say this will take you back to all of the notes. And then we'll just say cancel. And then the only thing I want to do here is change the background to gray-500.
So what I'm going to do is copy all of that, go back to our show view, and then I will update this so that we can be a little more consistent. Perfect. Okay. So now think about it. When I make a change here to the notes, well, I should be able to save or why don't we instead call it update. So right here we can say update. But when I click on that, what should happen? Where should the form submit?
Wiring Update Endpoint8:36
But when I click on that, what should happen? Where should the form submit? Okay, well, again, if we're following RESTful conventions, let's have a look. We're going to go into our routes file. Yeah, right here we could say, well, if we make a PATCH request to a specific note, that should update it. So why don't we do this? Router, listen for a PATCH request to /notes, and that will hit a controller called update. Okay, and this will be our last resourceful controller.
update. Okay, and this will be our last resourceful controller. Call it update.php. And once again, we are just going to say updating to confirm that we are hitting this controller. I do this all the time. Are we actually hitting this file or not? It's just a small little step that can save you some time. Okay, so let's make sure that when we edit a note and we submit the form, right up here, it submits the appropriate request. So now I'm going to visit /note, and remember, browsers do not support patch and
it submits the appropriate request. So now I'm going to visit /note, and remember, browsers do not support PATCH and DELETE requests, and you can see it's not going to allow that. But as we learned a number of episodes ago, we can now sneak it in by creating an input that is hidden, that has a unique name of _method, and a value that is equal to the request type that we actually want our code to treat the request as. So in this case, I'm saying, all right, well, I actually want you to treat this as a PATCH request. Our router will detect this and then route accordingly, right? So let's see.
Our router will detect this and then route accordingly, right? So let's see. I think this is probably right. Let's come back to the browser, give it a refresh, and click update, and there we go. We have responded to that patch request and loaded the appropriate controller. Cool. Okay, so now let's think about it. What needs to happen here when we hit this controller? And why don't we write it out as comments? Well first, let's find the corresponding note, right?
Implementing Update Logic10:36
And why don't we write it out as comments? Well first, let's find the corresponding note, right? Then maybe authorize that the current user can edit the note, and then we should probably validate the form. So for example, if you try to edit the note, but maybe you don't include anything or you include too much, well, that should trigger a validation error, in which case we should return here and provide you some feedback, okay? So validate the form. And then if no validation errors, update the record in the notes database table, right? These are basically the steps that we need to do here.
And then if no validation errors, update the record in the notes database table, right? These are basically the steps that we need to do here. Okay, let's get going. And once again, I can steal some of this code that we have here. So let's go into updates, find the corresponding note. So we have that right here. Select start from notes where the ID, but yeah, in this case, we have a POST request. So maybe we should pass through the notes ID as part of the form. That would be one way to do it. Okay, so if that's the case, we need to return here and add another hidden input called ID,
That would be one way to do it. Okay, so if that's the case, we need to return here and add another hidden input called id, where the value is equal to the id of the note that we wish to update. Again, this is one way that we could tackle it. Later, I'll show you how to include the identifier as part of the URI. But for now, we'll keep it as a hidden input. Okay, so back to our controller. Try to track down the notes. Let's then authorize that the current user can edit the note. So once again, authorize that the notes userID equals the current userID.
Let's then authorize that the current User can edit the Note. So once again, authorize that the notes userID equals the current userID. Next, we want to validate the form. So we will create a list of errors. And then let's just show you right here where we did it a number of episodes ago. We'll just steal this to save some time. Paste it in. All right, so that will automatically import our validator. Then we can say, all right, we're going to validate that the body, that textarea, meets our criteria.
Then we can say, all right, we're going to validate that the body, that textArea, meets our criteria. And again, notice as I'm doing this, this is one pain point that we might want to solve. We are now repeating validation logic. So I can see here, for example, that the body for a Note can be no more than 1,000 characters. But I also declare that logic here. So you can imagine situations where it becomes out of sync. For this form, your body logic is different than for that form. So yeah, these are things I want you to think about where you have little pain points and little pieces of duplication that could eventually become out of sync if you're not careful.
well, if we do have validation errors, then we need to reload that form. So for now, I'm just going to return the view. But again, later, we will learn about a process where if validation fails, you redirect to a specific controller. But again, we're not quite there yet, because we haven't yet talked about sessions and things like that, and flash messages. So for now, I'm just going to return the view directly. So once again, note edit.blade.php, I'm going to send, excuse me, I'm going to send through the errors. I will once again send through the heading, but again, we have some duplication there,
to send through the errors. I will once again send through the heading, but again, we have some duplication there, which is annoying. And then once again, we will pass through the corresponding note. All right, so this is what we get. Otherwise, if there are no validation errors, we can update the record. So we can just run an update query, DB query. And here's how we can write that. Update the notes table, and I'm going to set the body of the note equal to whatever body we have in the bound parameter.
Update the notes table, and I'm going to set the body of the note equal to whatever body we have in the bound parameter. But which one? Well, I can say where the ID of the note equals, and once again, another parameter. Okay, so the ID I care about is postId, and the body will be the body that the user typed into that text area, like so. Okay, finally, we're all done here. So the last step would be redirect the user. All right, so why don't we for now just redirect them back to all of the notes. So I can say header('Location: notes'), and then we're done here, so I can die().
All right, so why don't we for now just redirect them back to all of the notes. So I can say header, location equals notes, and then we're done here, so I can die. And yeah, I want you to notice a few pain points as we worked on this. As you'll find later, once you graduate to certain frameworks, we can simplify so much of this logic. Things like this, error handling, checking for errors, there are ways that we could simplify that dramatically. There are ways to remove this duplication. Obviously, in real life, you're not hard coding the current user. So just keep in mind, there are further ways that we can simplify this quite a bit.
Obviously, in real life, you're not hard coding the current user. So just keep in mind, there are further ways that we can simplify this quite a bit. All right, let's give this a shot in the browser. So I'll give it a refresh. Here's our notes, and I'll say has been updated. We update it. Oh, and it just worked. We didn't even have a mistake there. So I click on it, and that's what we have in the database. Let's confirm it.
So I click on it, and that's what we have in the database. Let's confirm it. If I switch over to TablePlus, there we go. Okay, but now, what happens if I switch back, if we fail the validation? And just to make this a little easier on me, why don't we make the validator, like, if it's more than 10 characters, something that will instantly trigger a validation error. Okay, well now, this alone would trigger an error. So I, let's do this, gibberish here. If I update it, all right, well, we have a couple of things here. Yes, we do see the validation error.
If I update it, all right, well, we have a couple of things here. Yes, we do see the validation error. And remember, we got that for free because we copied our view. So you'll see right here, just to make sure we're all on the same page, we already had this check from our create form, where we say, all right, well, if there is an error for the body, then let's display it as part of a paragraph tag. But also, I want you to notice that this is kind of annoying, potentially annoying. You type in something here. It does trigger a validation error, but when we update it, it reverts back to what you originally had.
Reviewing REST Conventions18:34
Okay, so all of that seems to be protected. All right, and I think that completes our first resource. Okay, and you know what? I want you to really focus on the conventions that we're following here. If you can, try to adopt them, and maybe that means taking out a sheet of paper right now and copying these down so you don't forget. So index will show all of a resource, give me all notes. show will show me a specific note. create will show me a form to create a new note. store will be what we hit after you submit that create form.
Create will show me a form to create a new note. Store will be what we hit after you submit that create form. So this is responsible for storing the note, for persisting the note. What else do we have here? Edit shows a form to edit a note. Update is where that edit form will submit. So that controller is responsible for updating a specific note. And then, of course, destroy is responsible for destroying the note, removing it from the database. So notice these conventions are being followed here in each of our controller names.
the database. So notice these conventions are being followed here in each of our controller names. And then we also mostly adopt that as part of our URI. The only thing that's missing here is really, rather than /notes, we would do something like /notes/{id}, and then when you update it, you would hit that as well. But we haven't yet talked about routing wildcards, and that's the only reason why we haven't adopted this, but we can talk about that in the future. All right, and yeah, that does it for your first initial CRUD app. And by the way, if you're not familiar with that term, CRUD stands for create, read, update,
All right, and yeah, that does it for your first initial CRUD app. And by the way, if you're not familiar with that term, CRUD stands for create, read, update, and delete. And that's exactly what we've done here. And yeah, if you think about it, so many of the things, so many of the tools on the internet really do break down to simple CRUD operations. For example, with our notes. Create a note, read a note, show me the notes, update that note, or delete this note. What about a to-do app? Create a to-do, show me my to-dos, delete this to-do.
