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

Why Errors Are Inconsistent0:00

Handling errors is one of the most important things that we can do within our application. And we kind of handle some errors, but we don't really do a good job of it. For example, inside of our TicketController and the show method. We attempt to find a ticket, and if it fails, we catch the ModelNotFoundException. And that's good, but there are so many other things that can go wrong with a simple database query. We would need to catch other kinds of exceptions as well. And while we could do that, we would need to do that for every other method. And that's getting to the point of not being very useful at all. But there's also another issue.

And that's getting to the point of not being very useful at all. But there's also another issue. Like, for example, let's make a POST request to create a new ticket, but let's cause some validation errors. If we submit this, we are going to get a response. Of course, we get a message, which is good. Then we have the validation errors that occurred. And that's good. We get them all so that we can see what problems we have with our validation. But if we take a look at the GET ticket, we have a completely different structure.

Override Global Render1:01

We get them all so that we can see what problems we have with our validation. But if we take a look at the GET ticket, we have a completely different structure. We just have a message and a status. So we aren't being consistent with our errors, and we need to be able to handle any error that occurs. And thankfully, it's pretty straightforward to do those things. All we need to do is go to our global exception handler, and we can override the render method. This is the method that executes for every exception, and it renders it. So in our case, we want to render those exceptions as a JSON structure, as a consistent JSON structure.

So in our case, we want to render those exceptions as a JSON structure, as a consistent JSON structure. So we will have two arguments. The first is going to be the request. The second will be the throwable exception. And then we could do something like this, to where we would return this error. We could add in our APIResponses trait so that we could return the error. But we're going to have to supply some different information here. So let's do this. Let's modify our error method.

Redesign Error Response Format2:05

So let's do this. Let's modify our error method. We use this in so many other places, and we will probably have to touch every one of these. But in order to make things work as we are implementing all of this, we're going to change this. So we'll have simply errors, which is going to be an array. Then we will have the statusCode, which we will initialize as null. And we'll do this. If errors is a string, then we will keep doing what we were doing. We don't want to break that functionality yet.

If errors is a string, then we will keep doing what we were doing. We don't want to break that functionality yet. But of course, we will need our message to be the errors argument there. Otherwise, we will return response, we'll call json, and then we will have our errors, and then the errors. So the idea here is that we will have a data structure that looks like this. We'll have errors, which will be an array. And then inside of that array will be the individual error objects. So in the case of a validation exception, we would have multiple objects because there could be multiple fields that are invalid.

So in the case of a validation exception, we would have multiple objects because there could be multiple fields that are invalid. But for a model not found, we would just have a status of 404. I don't necessarily think we need the type in those cases because the status kind of serves as that type. But if it's just a generic exception, I think the type would make sense. Then we could have the value and then the source. So for a validation exception, the source would be the attribute that the validation exception occurred for. So let's do this.

exception occurred for. So let's do this. We will return our error. And for just generic exceptions, we'll have our type. And we'll call get_class. We'll pass in the exception. And that will be the type of exception that occurred. Then we will set the status as zero. Because if it's just an uncaught exception, we don't necessarily know what that status would be.

Because if it's just an uncaught exception, we don't necessarily know what that status would be. Some exceptions would have a status that we could use, but the majority aren't. The majority are going to be something that doesn't really represent an HTTP status code, or it's not going to be useful to the end user. So we'll just leave status as 0. Then we will have our message, which we could have our exception message. And then this is going to be way too much information. But we'll have a source. But for these generic exceptions, don't do this.

But we'll have a source. But for these generic exceptions, don't do this. Just don't do this. This is for an example only. Because I'm going to include the line number by calling GetLine. And then we are also going to include the source code file name. As I said, don't do this. This is providing too much information to the end user. And while you might think, well, I want to see this information, yes. That's what logging is for, not for providing this to the client.

And while you might think, well, I want to see this information, yes. That's what logging is for, not for providing this to the client. Because this is a security risk. If you provide too much information to the end user, that information could be used in an attack. So this source implementation, once again, don't do this. This is only for example purposes. So for our ValidationException, we can send that. We now have our new error format. We can see that the class is ValidationException,

We now have our new error format. We can see that the class is ValidationException, although we're getting too much information there as well. I just want the class name. We don't need the fully qualified class name. Status is zero. We get the message, but notice that and one more error. So we want to also not just provide a consistent structure. We don't want to handle just every exception. We also want to provide some custom error objects for

We don't want to handle just every exception. We also want to provide some custom error objects for like validation errors or model not found exceptions. So we will accomplish that here in a little bit. And then source is all of the source code information, which once again, we don't want or need to provide in an error message to the client. But for our model not found exception, we're still doing that. So what I want to do inside of TicketController, I want to do this. I want to use all of the features provided by Laravel. So I want to use model binding.

I want to use all of the features provided by Laravel. So I want to use model binding. So we don't have to try to find or fail the ticket, and we don't have to catch a model not found exception. All we need to do is that, and that would be fantastic. So now, if we attempt to find this, we should see an error in our new format. We see model not found exception, status is zero, and all of that other information. Let's do this, though. Inside of our handler, I don't want to provide the fully qualified class name.

Let's do this, though. Inside of our handler, I don't want to provide the fully qualified class name. So let's start by writing a $className variable, where we will call getClass an Exception. Then we will get the index of the last set of double backslashes. Then for the type, we can call substring, pass in the $className, and we want to start at the index plus one. So now that should give us just ModelNotFoundException. For our ValidationException, we should get just ValidationException. So now for these other kinds of exceptions,

Custom Validation Error Rendering6:57

For our ValidationException, we should get just ValidationException. So now for these other kinds of exceptions, I want to provide some custom rendering. So let's do this. We can check if the exception instance of, or actually no, we don't have to do that, we have our class name, don't we? We could check if our class name is ValidationException class. Then we want to iterate over all of the errors. The errors is an array where the keys are the name of the attributes, and then the values is another array,

The errors is an array where the keys are the name of the attributes, and then the values is another array, because there could be multiple errors for a single attribute. So we would need to do something like this, to where we would have our key and value. But then we would need another foreach, so that we iterate over the value as message. So that then we could build our errors, and it could look something like this, to where we would have the status, which would be 422, then we would have the message, which would be our message.

it could look something like this, to where we would have the status, which would be 422, then we would have the message, which would be our message. And then finally, we would have the source, which is the attribute, and that is the key, so that then we could return this error and passing in errors. So I think that should work. We do need to import ValidationException, but we need to import the correct one, and that is Illuminate\Validation\ValidationException. Even though we're getting a red squiggly here for errors, that should work okay. So let's go back, let's try this. We will send that request.

So let's go back, let's try this. We will send that request. Sure enough, we get our structure. So we have our errors, which is an array. We have one object that has the validation information for the description field. Then we have another object that has the validation for the status. So that's perfect. Now we can do the same thing for the ModelNotFound. So we could check if the class name is ModelNotFoundException.

Refactor With Handler Map8:49

Now we can do the same thing for the ModelNotFoundException. So we could check if the class name is ModelNotFoundException. And if so, then we can do that. But the problem with this approach is that as we add more custom renderings for other kinds of exceptions, our render method is going to be very long. And I don't want to do that. So instead, I think we could do something like this. Let's say that we would have a protected field here, which we will call Handlers. And Handlers would look like this,

which we will call Handlers. And Handlers would look like this, to where we would have the ValidationException class, that's going to be the key, and then the value is going to be a method name, so handleValidation. Then we can have the ModelNotFoundException as the key. Then we will have a method name called handleModelNotFound. And so inside of our render method, we could check to see if the Handlers array has the key of our class name variable. And if so, then we will get the name of that method, and then we will call that method.

And if so, then we will get the name of that method, and then we will call that method. We will pass that to the error method. So it would look like this, to where we call our method, then we pass in the exception that we want to work with, and that's going to handle that. So that way, we don't have all of this other stuff inside of our render method. In fact, let's just cut this out. Let's delete everything else inside of here, except we still want to do this, getting the substring of our class here for just our generic errors. But there we go, that would be our render method, and I like that approach.

getting the substring of our class here for just our generic errors. But there we go, that would be our render method, and I like that approach. So let's have our private, or yeah, let's do this here, private function HandleValidation(). We get the validation exception as our argument, then we can paste in that code, and that should work. Let's copy that, let's paste it, let's rename this to HandleModelNotFound. The exception that we work with is a ModelNotFoundException, and this is going to be much easier. To where we will return an array that has the status, which will be a 404.

this is going to be much easier. To where we will return an array that has the status, which will be a 404. Then we would have the message, which in this case, we could say that the resource cannot be found. And then finally, we could have the source. I don't know what we necessarily want to do for the source. I mean, we could use the getModel method to get the model name. That might be providing too much information, but I don't know, I don't know. For right now, we'll run with that. That, as I mentioned, that might be just too much information.

For right now, we'll run with that. That, as I mentioned, that might be just too much information. Instead of returning this error here, we just need to return errors. Okay, so that should work. Our render method is, well, it could be a lot longer, we'll say that. And let's try this out. So for our validation exception, we shouldn't see any difference. That's working just as it should. And for our ModelNotFoundException, we see, yep, we are getting everything that we should.

And for our ModelNotFoundException, we see, yep, we are getting everything that we should. We see the status is 404, message is that the resource can't be found, and that source is ticket. Although, let's go back, we should not have the type, yep. And I don't think we need the type. The status in these cases gives us the type, basically. 422 is essentially a validation exception, 404 is a ModelNotFound. So I think that's good. And so now we have the ability to handle every exception that occurs within

Update Controllers and Add Auth Handling12:16

So I think that's good. And so now we have the ability to handle every exception that occurs within our application. We have the ability to provide custom rendering for certain kinds of exceptions. Now we just need to modify our controllers. And I'm not going to modify everything on screen, because you don't want to see me do that. But we'll at least do the TicketController here. So for our update method, we will get the provided ticket.

But we'll at least do the TicketController here. So for our update method, we will get the provided ticket. We no longer need to try that or find or fail. We don't have to do anything. And even though we could apply this same approach to our isAble, we could go back and undo everything that we did in the previous episode. I like this approach. I like seeing the if the user is able to do this. But as far as our error is concerned, we could do something like this, to where we have a method called notAuthorized.

But as far as our error is concerned, we could do something like this, to where we have a method called notAuthorized. And then inside of our API responses, we could implement that, protected function notAuthorized, to where we would have our message. But then we would simply return this error, where we would have our message would be the provided message. The status would be 401. And I don't necessarily know what we would do for the source, but we'll just provide that as an empty string there. So that way, we can still have the same functionality,

but we'll just provide that as an empty string there. So that way, we can still have the same functionality, the same overall code structure, but it's simplified. So we can go on to the replace. We don't need the ticketId anymore, because we can now use model binding. Don't need to findOrFail. Don't need to catch the model not found. And then we will return notAuthorized. Pass in our message, and we are good there. And the destroy method is going to follow the same pattern.

Pass in our message, and we are good there. And the destroy method is going to follow the same pattern. We now have our ticket as our argument. Get rid of the try, get rid of the catch. If the user is not able to delete, then they are not authorized. And that's it. I think that covers everything inside of the TicketController. So let's scroll up just to make sure. Looks like that is indeed the case. Except for our store method, we need to change error to notAuthorized.

Looks like that is indeed the case. Except for our store method, we need to change error to notAuthorized. But that's it. Everything else should be okay. So if we try to delete a Ticket that does not exist, let's try to delete the Ticket with an ID of 1,000. We should get a model not found. I'm not going to test the put or the patch, but let's do this. Let's sign ourselves out, just so that we can see the notAuthorized method work. So let's try to delete a Ticket that does exist.

Let's sign ourselves out, just so that we can see the notAuthorized method work. So let's try to delete a ticket that does exist. We know we have a ticket with an ID of 1. If we try to send that, we get an authentication exception. Now, I didn't expect this response, but this makes perfect sense. Because since we are not authenticated, we aren't even making it to our code to check if the user is able to delete the ticket. Because this is done for an authenticated user. So I wasn't thinking whenever I thought about that. But this gives us an opportunity to go ahead and

So I wasn't thinking whenever I thought about that. But this gives us an opportunity to go ahead and handle that with a custom rendering. So let's just do that right quick. And we will call this method simply handleAuthentication. And then we just need to implement that. I'm going to copy the handleModelNotFound, because this should also be a pretty simple implementation here. So that our exception is authentication. Our status is going to be 401.

So that our exception is authentication. Our status is going to be 401. The message is that you are not authenticated. Although, let's see what this says. This just says unauthenticated. So I guess we could just stick with that, unauthenticated. And I don't necessarily know what to do for the source. So let's just have an empty string there. So that should work in that case. But let's sign in.

So that should work in that case. But let's sign in. I don't remember what the user is, manager. We need a user with less privilege, which I just happen to have. Let's send it. We will update our token. So let's grab that. We'll go to our environments, globals, our bearer token, and save it. Now let's try to delete a ticket that does not belong to this user. I'm going to guess that ticket with an ID of one does not belong to our user.

Now let's try to delete a Ticket that does not belong to this User. I'm going to guess that ticket with an ID of one does not belong to our User. We'll find out. And we get a response that, once again, I didn't expect. But it makes sense because I think inside of our ApiController, we are handling the authentication exception. We need to handle the authorization exception. So that wasn't even going to work anyway. So let's try it again. We send that, and we are good to go.

So let's try it again. We send that, and we are good to go. So we can now handle any exception that occurs. And not only can we handle every exception, we are able to provide custom rendering for whatever exception that we need to provide custom rendering for. So we're good. Recovered. Now I just need to update our other controllers.

Now I just need to update our other controllers.

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