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

Creating Resources Overview0:00

We have spent a lot of time focusing on fetching data. We can fetch a collection of data, we can filter that, we can sort it, we can also fetch individual resources. And I'm sure that there's something that I'm forgetting, but for the most part we're done as far as fetching data. Now we need to do everything else, such as creating new resources, editing resources, and deleting resources. So in this episode we are going to focus on creating resources. That is of course done through a POST request, which is handled by the store method on our controllers. Now whenever we created our controllers, we opted to create some request classes, like the StoreTicketRequest. This is where we will validate the POST request for our ticket, and we will of course take that

Authorizing and Validating0:42

create some request classes, like the StoreTicketRequest. This is where we will validate the POST request for our ticket, and we will of course take that data once it's validated and store it in the database. I'm going to start here with the authorize method, and I'm just going to return true. Now in a typical application this is where at the very least we would determine if the User is authenticated, but we don't need to do that because the Sanctum middleware is protecting these controllers. So therefore a client will not even be able to make this POST request. They can make the POST request, but it's not going to work because if they haven't authenticated and they don't have that API token, this isn't going to be accessible at all. So all we need to do

work because if they haven't authenticated and they don't have that API token, this isn't going to be accessible at all. So all we need to do right now is return true. We will talk about authorization because not every client will be able to create a new ticket, but we will get there later on. Right now we're just going to return true here. Then the validation rules. Now in order to know how to validate the incoming data, we need to know what format that data is going to be, and we get to define what that is. Now since this is a JSON API, it's typically convention that the request format would mirror our payload. So the client would send us a request that would have a data object, that would have an attributes object, which would have our title,

mirror our payload. So the client would send us a request that would have a data object, that would have an attributes object, which would have our title, description, and status, and then we would also need the relationships object because we would need to know the author_id that created the ticket. So it's going to be fairly the same. We can omit the types, I think, because we really aren't checking those types, and really that wouldn't be a big deal except for our relationships. If we had different types of data for author or things like that, then that would make sense, but in this case it doesn't. So what we need to do is define our validation rules based upon our payload format. Now we could start by specifying data, that data is going to be required, and it's going to be an

define our validation rules based upon our payload format. Now we could start by specifying data, that data is going to be required, and it's going to be an array, and then we can say data.attributes is also required, and it's also an array, so that then we can say data.attributes.title, and yeah we could do that. All we really need to do is validate our attributes here. So we can say data.attributes.title is required, and it is a string. That's really all that we need to do, but of course we need to include the description, which is required in the string, and then the status. But the status is a little bit different, because we have some valid values that it has to be. Like the client can't say that the status is T, because our application has

status is a little bit different, because we have some valid values that it has to be. Like the client can't say that the status is T, because our application has no idea what T means. So we have A for active, C for completed, H for on hold, and then X for canceled. And then finally we need to validate the author ID, so that would be under relationships, and then author.data.id, and in this case it would be required and an integer. So with that in place, we should be able to send a POST request, and our TicketController will handle it, and everything will be validated. So before we start saving something into the database, let's first of all test this functionality. Let's create a new request under our tickets collection, and this is going to be a POST request. Now since this is a POST

Testing POST Requests4:09

of all test this functionality. Let's create a new request under our tickets collection, and this is going to be a POST request. Now since this is a POST request, this is going to go to the URL of just /tickets. We don't specify an ID or anything like that. We do, however, need to set the authorization to a bearer token. For our headers, let's set that Accept header to application/json, and then let's send it. Of course, we haven't sent a body, but this is a perfect opportunity to see the error messages that we get. Let's go ahead and let's save this as create, no, as POST tickets. Of course, our error messages tell the client exactly what is wrong here. title is required, description is required, and status is required. So we need to add a body. This is going to be a

tell the client exactly what is wrong here. title is required, description is required, and status is required. So we need to add a body. This is going to be a JSON payload. So let's start there to where we will have our data, which will then have our attributes, which will then have title. And let's call this firstTicket, because really this is the first ticket that we are creating. And if we send this, of course we will see that the title is now validated. Everything is fine there. Now we just have the description, status, and the authorId. So let's add the description, which can be, you know, something descriptive for the first ticket. This is the first ticket we created, and let's go ahead and add the status. But let's add an invalid status like T, because, well, why not? Now if we

first ticket. This is the first ticket we created, and let's go ahead and add the status. But let's add an invalid status like T, because, well, why not? Now if we submit this, we will then see, well, we're seeing what I did not expect. Validate string n does not exist. Did I type something wrong? Let's look at our rules, and yes, that needs to be a |, not a ,. So there we go. Let's resend this, and we can see that the selected data.attributes.status is invalid. Yeah, that's okay, because it is invalid. But in this particular case, we kind of need to tell the client why it's not valid, because the client needs to know that. And, of course, our documentation would have that, but having that in our message would be good too. So let's write a public method called

Custom Validation Messages6:25

know that. And, of course, our documentation would have that, but having that in our message would be good too. So let's write a public method called messages, and we will simply return an array. And we want a custom message for data.attributes.status. And we'll just say that the data.attributes.status value is invalid. Please use A, C, H, or X. And that's pretty specific. So if we go back, send that request, we should see that message. We do, so great. Let's change that to a valid value, A for active, and now we just have the authorId. So let's add that. That is under relationships, and then author, and then I believe it's data, and then we have id. And let's use the userId of 1. So if we send that request, that's great. No more validation errors. Everything should

Storing Ticket in Database7:16

believe it's data, and then we have ID. And let's use the userId of one. So if we send that request, that's great. No more validation errors. Everything should be fine. So let's implement this code inside of our TicketController. We don't have to worry about the validation, because that's automatically done. So if this code is going to execute, that means that we have a valid request. We have information. However, one thing we should do is check to see if the provided user exists. So let's do this. We'll say $user equals, we'll use our User model, and we'll find by the request input, and what is it? data.relationships.author.data.ID. That's very long. And you know what? No, let's not do that. Let's do this. We'll findOrFail, and we're gonna wrap this with a try catch.

dot data dot ID. That's very long. And you know what? No, let's not do that. Let's do this. We'll findOrFail, and we're gonna wrap this with a try catch. And if we catch that the ModelNotFoundException, then we need to tell the client this User does not exist. And here we could say, okay, oh you know what? We didn't ever add that trait to our APIController, did we? So let's do this. Inside of our APIController, you want to use the APIResponses so that inside of our TicketController, we can return this. Now we could spend a lot of time talking about what is the correct thing to do in this particular kind of case, because, you know, you're gonna find different answers based upon whoever it is that you ask. If you look at a lot of APIs, not just Laravel APIs, but just APIs

because, you know, you're gonna find different answers based upon whoever it is that you ask. If you look at a lot of APIs, not just Laravel APIs, but just APIs in general, you'll see that they always return a 200. It doesn't matter what happened. If it was a server error, or if the client sent a bad request, the API would return a status of 200. But the payload would have information about what actually happened, and it might actually have the actual status code in the payload. But the status code returned in the response would still be 200. Now, there are many reasons why you would want to take that approach, and one of those is security. Because one thing an attacker will look for is a server error, a 500, or just something that in their minds might be something

one of those is security. Because one thing an attacker will look for is a server error, a 500, or just something that in their minds might be something that they need to target to find a vulnerability. And if you return 200, you're kind of obscuring any kind of error. Now, of course, you might ask, well, if we return a 200, but our payload has the error information, you know, what is that protecting us from? And that's a very good question. But I will say that an attacker is going to primarily look at the status codes, because they have automated tools that send thousands of requests. They don't have time to look at each and every response. But if they look at the status code, and their tools are set up to look at specific status codes,

They don't have time to look at each and every response. But if they look at the status code, and their tools are set up to look at specific status codes, then you do protect yourself through obscurity. It's not true security, but it's better than nothing. So I think in this particular case, we will return 200 OK, but our message is going to say user not found, and then we could include an error that says the provided user ID does not exist. And I guess we need to look at if we returned any other error before, just so that everything is consistent. So I believe we did that inside of the AuthController, didn't we? So let's take a look. Did we have any errors anywhere? Yes, but we called error. We'll just stick with what we have.

auth controller, didn't we? So let's take a look. Did we have any errors anywhere? Yes, but we called error. We'll just stick with what we have. And if we need to change it later, we'll change it later. All right, so we'll have that done. But if we make it past there, then it's just a matter of creating our database record. So we could do something like this, to where we would set the title, and then we would grab the title from the request. So we would use input here. We would specify data attributes title. Then we will do the same thing for description and for the status. But we also need to specify the user_id,

Then we will do the same thing for description and for the status. But we also need to specify the user ID, which is provided through our input as well. That would be data.relationships.author.data.id. Now, we do need to add these as fillable to our model. So we will do that here in a minute. But since we are almost done here, we will say ticket create. We will pass in the model. And really what we need to do is return that, because we are creating a new resource. We want to return that resource after it is created, so that the client can do whatever it needs to with it.

We want to return that resource after it is created, so that the client can do whatever it needs to with it. So with that done, let's go to our Ticket model and let's add protected $fillable. And basically, we want the title, the status, the description, and the user_id. That should get us there. Now we just need to submit this request. So whenever we send this, we should get a response back of our entity. But one thing we did not do is use our TicketResource, because yes, we returned the entity. We can see that we have a new id here.

because yes, we returned the entity. We can see that we have a new id here. However, we really needed to do this new TicketResource, passing in that newly created ticket, and that would be it. So I guess technically, this is not the first. This is the second ticket. So we will add the second ticket. And let's change the userId. In fact, let's test this out so that we try a User that does not exist. So id of 15, I believe we had tried to use that previously and that didn't work. So if we send this, yes, we get an error.

So ID of 15, I believe we had tried to use that previously and that didn't work. So if we send this, yes, we get an error. The provided user ID does not exist. I misspelled exist. But that works fine. That's great. So let's use an ID of two. That is going to create our new ticket, and it is now in the format that the client expects. So we are good to go as far as creating our resource. But we also need to do this inside of our AuthorTicketsController, because we need to be able to create a ticket using the URL of authors,

Creating via Author Route13:35

But we also need to do this inside of our AuthorTicketsController, because we need to be able to create a ticket using the URL of authors, and then the authorId tickets, and then submitting a POST request. So that should be easy enough to do. Let's duplicate this request. Let's rename it to be POST user ticket. Let's change it from a GET to a POST. Let's copy this payload. But this is going to be slightly different, because in this particular case, we don't need to specify the userId,

But this is going to be slightly different, because in this particular case, we don't need to specify the user ID, because the user ID is part of the URL. So this relationships part is not needed. All we really need is just the attributes. So this means that we are going to need to modify our storeTicketRequest, and specifically our validation rules, because we only need to validate the author ID for a certain route. So let's do this. Let's create a variable called rules, where we will set our base rules here.

So let's do this. Let's create a variable called rules, where we will set our base rules here. And then we will kind of do what we did inside of our resource classes, to where we'll check that the route is tickets.store. If it is, then we will add the rule for the author ID, so that that will be available for our TicketsController. But for our author tickets, then all we will need are the attributes. So let's go back to our store. We do need to return our rules here. But with that, we can go to our AuthorTicketsController.

We do need to return our rules here. But with that, we can go to our AuthorTicketsController. Actually, let's do this. Let's go to our TicketController. And we don't necessarily need our try and catch in that particular case. Let's just grab what we have as far as creating our model. And let's drop that. Okay, we don't have that method. So let's copy the entire method from our TicketsController. And let's paste that inside of our AuthorTicketsController.

So let's copy the entire method from our TicketsController. And let's paste that inside of our AuthorTicketsController. Now, we do need to change a few things here. First of all, we need to add a use statement for our StoreTicketRequest. We also need to specify the authorID for this store method. We should be fine as far as that is concerned. We do need to change the value for the userID because we have that passed to the method. That should do it. That should get us exactly what we need.

That should do it. That should get us exactly what we need. So let's do this. Let's say that the authorId is going to be 6 and tickets. We're going to change this to thirdTicket and we will send that request. We should get the response back for our new ticket. We can see we have a new ID. We have all of the ticket information. So now we have the ability to create a ticket resource, both from our TicketController and from the AuthorTicketController.

So now we have the ability to create a Ticket resource, both from our TicketController and from the AuthorTicketController. And in the next episode, we will look at how to delete resources.

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