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

Using Scribe for Docs0:00

Writing documentation is probably the most boring thing that we can do for any project. But unfortunately, it's something we have to do. I would prefer to just move on and do something else, but this is an API. We need to provide documentation so that people who want to use the API will know how to use the API. So we could of course write it ourselves, but no, we don't want to. Instead we are going to use a package called scribe. This generates API documentation from our Laravel codebase. So this is going to do a lot of the work for us, but there's still some things that we will have to do manually.

Installing and Publishing Config0:36

So this is going to do a lot of the work for us, but there's still some things that we will have to do manually. But first things first, let's install scribe. And we can do so with composer require --dev, and then the package is called knuckles/wtf. And then we will want to publish the config. So with php artisan vendor:publish, the tag is scribe-config. And let's take a look at the config. It's called simply scribe.php. It is inside of our config folder, and it's a configuration file. There's a lot of things here though that we need to talk about.

Configuring Routes and Output1:09

It is inside of our config folder, and it's a configuration file. There's a lot of things here though that we need to talk about. So first is the title. This is, as it says, the HTML title element for the generated documentation. So let's just say, tickets please, API documentation, and we'll leave everything else alone. This base URL, there's another base URL that we will talk about here in a moment. And the next is routes, and this is where things get interesting. Because scribe is going to look at our routes, and it is going to automatically generate documentation based upon those routes. So the routes go to controllers, and some of those controllers use request classes.

documentation based upon those routes. So the routes go to controllers, and some of those controllers use request classes. So it's going to document all of those things, and it needs to know where to look. Now thankfully, there's nothing that we need to change here, because all of our routes begin with API. So we're good here. We can keep on going down. But then if we wanted to include any routes, we could do that. Or if we wanted to exclude any routes, we could do that as well. But then we can also specify how our docs are generated. Are they generated as static files?

But then we can also specify how our docs are generated. Are they generated as static files? Or if we use Laravel, then it's going to create a Blade view so that we can add routing and authentication. I'm going to go with the static route, because me personally, I like to not have to authenticate to look at the API docs. So that's what I'm going to leave it as. And you can choose where you want those static documents to be published. public/docs is fine for me. But then there's options for the Laravel approach. And then let's scroll on down to this try it out. This is a nice little feature, so that if the user has an API token, they could try the endpoints from our documentation.

Setting Auth and Base URL2:47

And then let's scroll on down to this try it out. This is a nice little feature, so that if the User has an API token, they could try the endpoints from our documentation. So I'm going to leave that as enabled. And here, the base URL needs to be something that works. So for us, that is going to be localhost:8000. Or at least for me, this is what it's going to be. Of course, if we were publishing these out on the internet for people to actually use, this would need to be the base URL that they would access. But this is going to be okay for us. Use CSRF?

But this is going to be okay for us. Use CSRF? No, because we don't have any CSRF. And that's okay, because CSRF attacks really depend upon cookies. We're not using cookies. And then authentication. This is the important part. So this asks, how is your API authenticated? And then it says, set this to true if any endpoints in your API use authentication. So practically all of ours does, so that is of course going to be true.

And then it says, set this to true if any endpoints in your API use authentication. So practically all of ours does, so that is of course going to be true. But then the next option is the default. What is the default? Is the default going to be that everything is authenticated or that nothing is authenticated? Well, practically everything is requiring the user to sign in, so we're going to set the default as true. Now, not every endpoint requires the user to authenticate. For example, the login endpoint, we don't want to limit that to authenticated users, so we'll have to make note of that in our documentation. But that's easy enough to do.

so we'll have to make note of that in our documentation. But that's easy enough to do. But then we get to specify how that authentication is handled. It is a bearer token, and it is inside of the Authorization header. So the name is going to have the Authorization value. And then here, we can add an API key to our .env file, or we can hard code this. And I'm just going to hard code this. And we're going to start with just a normal User. And ideally, we won't, but I'll show you why we don't want to use a normal User. So let's first of all get a token there so that we can copy that and paste it inside of our scribe config.

And ideally, we won't, but I'll show you why we don't want to use a normal user. So let's first of all get a token there so that we can copy that and paste it inside of our scribe config. And then this placeholder is just what is going to be used in the documentation any time that the API key is used. So in the documentation, it's going to say your auth key. That's fine. It's arbitrary. You can change that to whatever. And then everything else is going to be okay. So let's close that. And now let's generate our documentation.

Fixing Generation Errors5:15

So let's close that. And now let's generate our documentation. So we will use php artisan scribe:generate command. And we're going to see some red flash by. So let's scroll up and let's take a look at the red. It says that there was an attempt to read property id on null. And it was as it was processing the post request on our TicketsController. Well, we don't use the id in our TicketsController. Let's go to our TicketController. Let's go to the store method.

Let's go to our TicketController. Let's go to the store method. And yeah, I mean, we check if the User is able to create a ticket. But we don't do anything with the ID here. But let's go to the StoreTicketRequest class. And we do use the ID here, right here. As we are dynamically building our rules, we refer to the userID. So this might seem a little weird because, well, the user is authenticated. We gave scribe an API token to use to authenticate and should be able to document all of this stuff.

We gave scribe an API token to use to authenticate and should be able to document all of this stuff. But here's the thing. So scribe is looking at our TicketController and is processing the store request. It sees that the store request accepts a StoreTicketRequest. So it's not going to execute this method at all. Instead, it is going to process the rules on the StoreTicketRequest. Because when it comes to a request, that's really all that matters. What are the rules for that request?

Because when it comes to a request, that's really all that matters. What are the rules for that request? So as scribe starts to process these rules, it's encountering this ID. Well, we get the ID from the user from the request. And if there is no request, there is no user. So this is one thing that we need to be aware of. Because now we need access to that user object. And the way that we can do that is through the auth facade. So we'll just call user(), and that's going to solve our problem. Although we've used the ID someplace else,

So we'll just call user, and that's going to solve our problem. Although we've used the ID someplace else, or we've used the request to get the user. And was that updateTicketRequest? Yes, right there. So we need to change this to use the Auth facade as well. But that's easy enough to do. Simple change, we're done. And so now let's generate our API. We won't see any errors.

And so now let's generate our API. We won't see any errors. I didn't see any red fly by. So we are good to go. And we can view our documentation in the browser. So let's go to localhost:8000/docs. And here is our documentation. But things are, well, it's a little messy. Because we have this introduction, which is great. We have this authenticating requests,

Because we have this introduction, which is great. We have this authenticating requests, which tells the user how to authenticate their requests, which that's great too. But let's look at the end points. Not only is this all just kind of thrown in there, but a lot of these have the same text. Display a listing of the resource. That's not useful. Display the specified resource.

Improving Endpoint Organization8:18

That's not useful. Display the specified resource. Update the specified resource. So there's several things that we need to do here. First of all, we need to categorize or organize all of our endpoints so that we have like the authentication endpoints. And then we have managing tickets and then managing users. But then also we need some useful text here. If we take a look at the post api/login,

But then also we need some useful text here. If we take a look at the post API /login, we see that this says that it requires authentication, which we know it doesn't. Users can't be authenticated in order to sign in. So the first thing we need to do is address the login and logout so that it has the correct information. And the logout does, but still we want to organize these things.

Documenting Auth Endpoints8:59

And the logout does, but still we want to organize these things. So let's start with the AuthController. Let's scroll on down to our login method. And then we essentially want to add some documentation and we can add documentation in a couple of different ways. We can use doc blocks, which is what I'm going to use, but we can also use attributes. And we don't have to use just doc blocks or just attributes. You can mix and match if you want, but I like doc blocks,

And we don't have to use just doc blocks or just attributes. You can mix and match if you want, but I like doc blocks, or at least that's what I'm most familiar with. So that's what we're gonna do. So the first thing I want to do is specify that this login method does not require you to be authenticated. So we have a notation called unauthenticated. And so whenever we generate our documentation, this requires authentication is going to go away,

And so whenever we generate our documentation, this requires authentication is going to go away, but we also want to organize these things. So let's say for the text, because as far as the text over here is concerned, post API login, that's the title of this particular endpoint and I don't like that. I mean, because it's the same information right here. So instead I want this to simply say just login. So the title for this endpoint is gonna be login.

So instead I want this to simply say just login. So the title for this endpoint is gonna be login. Then we can add some extra information like authenticates the user and returns the user's API token. But then we can create a group with group and the group will be simply authentication. And let's see what else. This does not have an example response. This certainly gave us an example request,

This does not have an example response. This certainly gave us an example request, which if we look at the body parameters, we see that the email is a string and it must be a valid email address. And the password is a string and it must be at least eight characters. You know, it's getting that information from this LoginUserRequest. It processed the rules here

from this login user request. It processed the rules here and it generated the documentation based upon those rules. So a lot of this we didn't have to do. We just need to do some other things. So for our login method, we also want to supply a response. We can also say that for a 200 response, it's going to be a certain kind of response. And if we wanted to supply the response structure

it's going to be a certain kind of response. And if we wanted to supply the response structure for another kind of status code, we could do that as well, but we're gonna keep things simple. So as far as the sample response, I'm gonna take out the token. And I don't remember what that placeholder was, but I think it was like your auth key or something like that.

but I think it was like your auth key or something like that. So we'll just stick with that message and status. Okay, we're good there. So let's copy this block of comments and let's paste it for the log out. Now, of course, the response is gonna be different. In fact, we just return an ok. So our response is just gonna be an empty object. The group is still gonna be authentication.

So our response is just gonna be an empty object. The group is still gonna be authentication. This requires authentication. So we will get rid of the unauthenticated. And for this, we'll just call it signOut. And we'll say that this signsOut the user and destroys the API token. So with that done, let's generate our documentation again. And we're going to start to see that things are getting a little more organized.

And we're going to start to see that things are getting a little more organized. So now we have this authentication section, which has just the login and the log out. If we look at the login, we still see the body parameters, which is fine, but we also see a sample response for a status of 200 where the data, the token, the message and status. So that's great. That's awesome.

Documenting Ticket Endpoints12:31

So that's great. That's awesome. So now let's focus on our TicketController. So if we take a look at our endpoints, display a listing of the resource. Here is our tickets. And the really cool thing here is that we don't have to worry about a sample response. It's automatically giving us one. So we're good as far as that is concerned,

It's automatically giving us one. So we're good as far as that is concerned, but there's a lot that we need to do here because we have the sort query parameter, we have filters. We need to document all of those because those definitely aren't here. And really Scribe has no idea about those things. So we'll start with our index method and we will give this a title of getTickets, or maybe we should say getAllTickets

and we will give this a title of getTickets, or maybe we should say getAllTickets because that is exactly what it's gonna do. And then we can set this as a group of managing tickets. And then we need to start specifying the query parameters. And we do that with queryParam. And our first one is our sort, which is a string. And then our documentation for that is going to be that the data fields,

And then our documentation for that is going to be that the data fields, actually it could be a field or multiple fields, to sort by, we need to separate multiple fields with commas and denote descending sort with a minus sign. And then we can give an example of that. And let's say that we'll sort by title and then created_at descending. So that's going to be that. And if we generate our documentation, let's refresh,

So that's going to be that. And if we generate our documentation, let's refresh, we will now see our managingTickets, getAllTickets. We have the query parameters here, the data fields, you know, that description that we specified and then the example title and createdAt. Although maybe we should do this, sort equals and then title and createdAt. So we can supply an example or Scribe will generate an example for us.

So we can supply an example or Scribe will generate an example for us. But there are some times that we don't want or need an example. Like for example, filtering by the status, you know, because we have just certain values and there's really no reason to include an example here. So we can say that to filter by status code and that would be A, C, H or X. And then we could say that we don't want any examples.

and that would be A, C, H or X. And then we could say that we don't want any examples. So we will say no-example. But then we also have another query parameter for the filter. This is for the title and this is filter by title. Wildcards are supported. And then we can supply an example to where we can have an asterisk and like fix, the idea of fix this particular problem.

to where we can have an asterisk and like fix, the idea of fix this particular problem. Then what else do we have? We have created_at, you know, we have enough examples here. We're not going to document everything because that would, you don't wanna see that on screen. So we'll leave this as is, but let's copy this and let's paste it for the store method. And of course, there are several things that we need to change here.

And of course, there are several things that we need to change here. Like this is not getting all tickets. This is create a ticket. And then for the text, we can say creates a new ticket. Users can only create tickets for themselves and managers can create tickets for any user. And then we will leave this in the managing tickets group, but let's get rid of everything else. I think we will need to provide a response here,

but let's get rid of everything else. I think we will need to provide a response here, but let's generate our documentation so that we can see this because this is where we are going to spend most of our time here is for the create a ticket. Because if we scroll on down, this is great, authorization, you know, all that stuff. But look at the body parameters, data, object, optional. Data is not optional.

But look at the body parameters, data, object, optional. Data is not optional. Attributes, object, optional. That's not either because you have to have those in order to get to title, description and status. Now notice that title, description and status does not say optional. That's great. But we need to change our rules so that the data and the attributes are not optional.

Correcting Request Rules16:28

But we need to change our rules so that the data and the attributes are not optional. So that's easy enough to do. Inside of our rules, we can simply just add data to be required and an array, then data.attributes to be required and an array. And that's going to fix that particular problem. But let's also go back because if we take a look at relationships, that says that it is optional.

because if we take a look at relationships, that says that it is optional. Author is optional, data is optional and ID is optional. But notice here that it says that the ID must be one. Now, the reason why we are seeing that is because we signed in with just a normal User. So everything is going to be based upon that User ID of one. And that's not what we want. Instead, what we really want is no specific information as far as the ID is concerned for the user.

Instead, what we really want is no specific information as far as the ID is concerned for the User. So we are going to use the manager to get an API token so that we can generate the documentation using that API token, which means that we need to go back to our scribe.php file and we need to find that token value right there. So we will replace that with the manager token. Let's generate our documentation once again. And so now the ID for the User or the author.

Let's generate our documentation once again. And so now the ID for the User or the Author is going to be just ID. So that's great. And now we can see that data is not optional, attributes is not optional, but relationships is still optional as is author and data. But the problem here is that we are dynamically building our rules. So when the route is for our TicketsController,

we are dynamically building our rules. So when the route is for our TicketsController, then for the author ID, it is data relationships author data ID. That is a lot to type. So really what we need to do is say, is TicketsController to where we will set that to if the route is tickets.store. So we could use that variable there. And then we almost need to do something like this

So we could use that variable there. And then we almost need to do something like this to check if it is the TicketsController, then we will add in the necessary rules for that structure so that we will have data relationships is required and an array. Then we will add in the others, which what was it as relationships.author and then data. But then too, these rules have to be in order.

and then data. But then too, these rules have to be in order. So we can't really include this dynamic rule here. We need to do that down here to where we will say rules and then build this dynamically. But this is going to work just fine. And this is going to make it work for both of those controllers. So we can generate this again, let's refresh. We will now see that attributes

So we can generate this again, let's refresh. We will now see that attributes and relationships are required. If we drill down to author and data and ID, let's try to find where we create a ticket for the AuthorTicketController. So that would be store a newly created, there it is right there. Okay, so if we scroll on down here, we can see that data and attributes,

Okay, so if we scroll on down here, we can see that data and attributes, title, description and status are all needed here, but the relationships structure is not listed here because of course it was dynamically created. So we are good to go, except I want to add in some descriptions for title, description and status. So we can do this in a couple of different ways. The first is to use just dot blocks.

So we can do this in a couple of different ways. The first is to use just dot blocks. So for our store ticket request, we could add some dot blocks. And instead of using the query param that we did for the index method on our TicketController, we would have a body param and then we can specify the structure, which let's just try the title here. So we'll do title and we'll say that it is a string.

which let's just try the title here. So we'll do title and we'll say that it is a string and the tickets title. And I don't think we necessarily need an example so we can say no example there. And so that's going to be great if we generate our documentation and let's look at our body parameters. We can see that the title now has a description, and there's no example, but now it says optional.

We can see that the title now has a description, and there's no example, but now it says optional. So we have to go back here. We have to say string, but before we say string, we had to say required. Then we have to generate our docs again so that then we can refresh and we can hopefully see that the attribute is now not optional, but I guess I got it mixed up. Looks like the type needs to come before required,

is now not optional, but I guess I got it mixed up. Looks like the type needs to come before required, but you get the idea. We can do it that way. And that kind of takes away from what we were doing before. As far as using the rules, because the rules state that yes, the title is required. So this requires a little bit of extra stuff, but it still works except that then what if we do something with the relationships

but it still works except that then what if we do something with the relationships so that we have relationships, and I don't remember what this is. So let's just copy that whole thing and paste it in to where then that is an integer, which is required. And then we would say the authorId. Well, that's going to be fine for our TicketController, but it's not going to be fine

Well, that's going to be fine for our TicketController, but it's not going to be fine for our AuthorTicketController. Because now if we take a look at the documentation, we have data, we have attributes, which is fine, but then we have relationships. And relationships is not part of this particular request because the author came in as part of the URL, if you'll remember. So if we look at managing tickets though,

if you'll remember. So if we look at managing tickets though, then that's going to be fine. Relationships, author, data, ID, and the authorID. So if we dynamically generate our rules, we pretty much have to dynamically generate the documentation for those rules. So we can't really use our .env blocks here, which is fine, because we have a method that we can use that basically gives us the same thing.

Dynamic Body Parameter Docs22:22

because we have a method that we can use that basically gives us the same thing. So it's called bodyParameters. And we basically need to return an array where the keys are the attributes. So it would be for the title and description and things like that. And then the values are going to be the individual documentation for those things. So we could do something like this.

the individual documentation for those things. So we could do something like this to where we would return data, attributes, title, and then this would be an array to where we would have the description, which would be the tickets title, although we need this, the tickets title. And let's just have method here so that we can prove that this is coming from this method. And then, unfortunately, this doesn't work.

so that we can prove that this is coming from this method. And then, unfortunately, this doesn't work. It's supposed to according to the documentation, but it's not working. So evidently there is an issue, but we're going to leave this here and you'll see what this does, but we'll leave it here for when it does end up working again. So let's generate, let's go to our documentation.

end up working again. So let's generate, let's go to our documentation. This is for the TicketsController. So if we drill down, we can see that our title now has this description, the tickets title, and we can tell that it's coming from that method, but the example is no example. But if we omit example, it's going to generate an example for us.

But if we omit example, it's going to generate an example for us and that's not what we want. So we're just going to leave this as example as no example because that is supposed to work. So with this general idea, what we can do then is have something like documentation and then we will return documentation, but then we will dynamically build this so that first of all,

but then we will dynamically build this so that first of all, we will have the title, the description and the status, but then we want to check if this route is tickets.store, then we will add to our documentation for the author ID through the relationships. And we'll have a description that says the author assigned to the ticket. And then we would have an example of no example. But if this route is not for our TicketsController,

And then we would have an example of no example. But if this route is not for our TicketsController, then we have a different key here. It is simply just author. And then that's going to be fine. We dynamically generate our rules, we dynamically generate our documentation. So if we look at the documentation for our create a ticket for our TicketsController, let's drill on down to our relationships.

for our TicketController, let's drill on down to our relationships. We see that the author assigned to the ticket, and this is the funny thing too. So the example here is zero. It's almost like it knows it's not supposed to have an example. So it just has a value of zero, but yet if we omit the example, it'll still generate one. It's just interesting.

but yet if we omit the example, it'll still generate one. It's just interesting. So if we go back to endpoints and take a look at the store, a newly created resource authors. Yes, this is for the AuthorsTicketController. Then we can see once again, we have the data attributes for the title, description and status, but yet then we have the author by itself.

for the title, description and status, but yet then we have the author by itself. And so then it's just a matter of filling out the documentation for everything else, which I'm not going to do on screen because I wouldn't want to see me do it. I know that you don't want to see me do it, but the source code in GitHub will have all of the documentation just in case if you want to see it.

will have all of the documentation just in case if you want to see it.

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