Requirements and Background

Requirements:

The backend students who write interface documents for client colleagues have recalled on various occasions the blood and tears of handwritten documents before using automated document tools. My story is different because, first of all, I’m the head of the Android group at my company, and I belong to the client side of the aforementioned bloody history. But the history is the same, and without automated documentation, interfaces are the least efficient part of the development process. So you decided to use Swagger to build the process of generating documents from PHP annotations.

Background:

Our restful API project uses the Phalcon framework, which is simple enough to scan the Controller directory with Swagger. Our PHP API project is called PHp_API_project. The server uses Nginx.

Set up

Let’s start with what the final document generation process will look like, so that we can get a general idea: After construction, the entire process, from document generation to front-end presentation, is as follows:

  1. Write swagger/in the PHP file* comment/
  2. Run the bin/swagger. Phar command in swagger-php to scan the directory where PHP controller resides and generate swagger. Json file
  3. Copy swagger. Json file to the directory specified in index.html in swagger- UI
  4. Open the URL where swagger-UI is, and you’ll see the document. Each API in the document can be accessed directly from this site.

Swagger-php: a tool for scanning PHP comments. Swagger-ui: Used to display the contents of swagger.json files generated by the scan tool on a web page.

First download these two projects locally:

$ git clone https://github.com/swagger-api/swagger-ui.git
$ git clone https://github.com/zircote/swagger-php.gitCopy the code

Document generation tool deployment

The command bin/swagger is used to generate swagger. Json. The main work is to use Composer to solve the dependency. Because it is difficult to use native composer directly, it is better to set the native composer source. In this case, the entire document generation tool deployment is the following three lines:

$ cd swagger-php
$ composer config repo.packagist composer https://packagist.phpcomposer.com
$ composer updateCopy the code

As long as no errors are reported, the deployment is complete. After you have finished, you can generate a document to try it. There is an example PHP project under swagger- PHP directory, which has written various interface comments in Swagger format, let’s try to generate a document. Run the following command:

$ cd swagger-php
$ mkdir json_docs
$ php ./bin/swagger ./Examples -o json_docs/Copy the code

The command scans PHP file comments in the Examples directory and generates swagger.json files in the json_docs directory. This swagger. Json file is the API file that the swagger- UI front end displays.

NOTE: Swagger – PHP is just a tool, you can put it anywhere.

Front-end Swagger – UI Deployment:

The deployment method is simple in three steps:

1. Copy the dist folder in swagger-UI project to the phP_rest_API root directory.

NOTE1: just copy the dist folder. I’d better rename it, but I’m not going to rename it for simplicity. NOTE2: our project root directory is the same as nginx root. In fact, you don’t need to put it in a directory, just put it in a directory that can be accessed across domains without crossing domains. Why cross-domain problems? We’ll talk about that later.

2. Modify the index. HTML file in the dist folder and specify the swagger

I’ll just change one line. For simplicity, specify swagger. Json in the dist directory. Assume that the php_API_project host is api.my_project.com. Assume that the root specified in nginx for the php_API_project project is its root directory. Assume that the dist folder in swagger-UI is in the above root directory; Assume that swagger. Json file is going to put in the dist directory (php_api_project/dist/swagger. Json); Change the following snippet in index.html to something like this:

var url = window.location.search.match(/url=([^&]+)/); if (url && url.length > 1) { url = decodeURIComponent(url[1]); } else { <! - it is the line that is to you generate swagger. Json can be access to the path of the can - > url = "http://api.my_project.com/dist/swagger.json"; }Copy the code

3. Copy swagger. Json to the directory above.

Php_dir /json_docs/swagger.json php_api_project/dist/Copy the code

After the above steps are complete, visit api.my_project.com/dis… You can see the API documentation for the Examples small project.

Writing PHP comments

Swagger-php project Example already has many related examples, just copy and paste it. For more detailed documentation on the comment rules, see here: bfanger.nl/swagger-exp…

Suppose my project controller is located in php_API_project /controller/, then I only need to scan that directory, not the entire PHP project.

In order to swagger. Json generated in some unified configuration, establish php_api_project/controller/swagger. The directory holds a PHP file with no code, just comments.

I’ll call this file swagger.php and it looks like this:

<? php /** * @SWG\Swagger( * schemes={"http"}, * host="api.my_project.com", * consumes={"multipart/form-data"}, Produces ={"application/json"}, * @swg \Info(* version=" produces ", * title="my project doc", <br> < p style =" max-width: 100%; clear: both; min-height: 1em; <br> From now on, we will be happy here! <br> From now on, we will be happy here! < br > "*, * * @ SWG \ Tag (* name =" User ", * description = "User operations," *, * * @ SWG \ Tag (* name = "MainPage." * description = "homepage module", *, * * @ SWG \ Tag (* name = "News", * description = "News and information," *, * * @ SWG \ Tag (* name = "Misc", * description=" Other interfaces ", *), *) */Copy the code

As shown above, my PHP file doesn’t have a single line of PHP code, just comments, to define some global swagger Settings:

Host: the address of the project. It serves as the URL base for each interface. It is pieced together as Consumes for one phase. The interface accepts the MIME type by default. In my example, formData corresponds to the POST form type. Note that this is the default value of the project, which can be overwritten in produces: produces the MIME type returned by the default interface. @swg \Tag: Tag is used to classify documents, and the name field must be unique. An interface can specify more than one tag, and it will appear in multiple group categories. Tags can also be used without being defined here, but then there is no description. A little use will make everything clear.

Then it’s time to write swagger comments for each interface. Here’s an example:

/** * @swg \Post(path="/user/login", tags={" user "}, * summary=" login interface (user name + password)", * description=" User login interface, account can be user name or mobile phone number. Reference (This will result in a jump link on the page: [user login note] (http://blog.csdn.net/liuxu0703/), "* @ SWG \ Parameter (name =" userName ", type = "string", the required = true, In ="formData", * description=" login user name/mobile phone number "*), * @swg \Parameter(name="password", type="string", Required =true, In ="formData", * description=" login password "*), * @swg \Parameter(name="image_list", type="string", Required =true, In ="formData", * @swg \Schema(type="array", @swg \Items(ref="#/definitions/Image")), * description=" Well, no one asks for a bunch of photo information when they log in. "*), * @swg \Parameter(name="video", type="string", required=true, (ref="#/definitions/Video"), * description=" #/definitions/Video") Video? As above, for example @swg \Schema." *), * @swg \Parameter(name="client_type", type=" INTEGER ", required=false, in="formData", * description=" Type of client calling this interface: 1-Android, 2-ios. False "*), * @swg \Parameter(name="gender", type="integer", Required =false, in="formData", * default="1", * description=" gender: 1- male; 2- female. Note that the default value of this parameter is not the default value of this parameter, but the default value of this parameter is filled in on the Swagger page, in order to facilitate the access of this interface with swagger. * ) */ public function loginAction() { // php code } /** * @SWG\Get(path="/User/myWebPage", tags={"User"}, Produces ={"text/ HTML "}, * summary=" User's personal web page ", * description=" This is not an API interface, this returns a page, so PRODUCES text/ HTML ", * @SWG\Parameter(name="userId", type="integer", required=true, in="query"), * @SWG\Parameter(name="userToken", Type ="string", Required =true, in="query", * description=" user token ", *), * ) */ public function myWebPageAction(){ // php code }Copy the code

The rules are simple, and everyone can understand them by looking at the code. If you don’t understand, go to the documentation…

The above login interface uses two structured data, one is an array of type image, One is a video-type structure. (The structured argument can only be used when in=”body”, but that doesn’t stop us from passing the structured data as JSON as a string to simplify things. We just need to document this structure.)

Swagger can also be defined with PHP comments:

<? php /** * @SWG\Definition(type="object", @SWG\Xml(name="Image")) */ class Image { /** * @SWG\Property() * @var string */ public $url; /** * @SWG\Property(format="int32") * @var int */ public $height; /** * @SWG\Property(format="int32") * @var int */ public $width; } <? php /** * @SWG\Definition(type="object", @SWG\Xml(name="Video")) */ class Video { /** * @SWG\Property() * @var string */ public $url; /** * @SWG\Property() * @var string */ public $thumb_url; /** * @SWG\Property(format="int32") * @var int */ public $length; /** * @SWG\Property(format="int64") * @var int */ public $size; }Copy the code

PHP /bin/swagger/Image/Video /swagger/Image /bin/swagger

The advantage of this is that the structure is displayed in the interface parameter document so that the client knows which structure to pass.

My interface chestnut does not write response rule, because we use JSON as return carrier, return error code is also contained in the JSON structure. In addition, most of the json formats returned by interfaces are very complex, which cannot be described by Swagger’s response rules.

Swagger’s response writing rule is based on HTTP response code (404, 401, etc.). In short, this description rule is not suitable for our interface.

So I just dropped the response description and used swagger to request the interface in place to see what was returned. Otherwise, the return information of the interface is described in description.

PHP /bin/swagger /swagger /swagger /swagger /swagger/PHP /bin/swagger /swagger /swagger/PHP /bin/swagger /swagger/PHP /bin/swagger /swagger/PHP /bin/swagger /swagger/PHP /bin/swagger

NOTE:

As you can already see, interface comments don’t have to be written on the interface. You can still generate documentation by writing comments out of thin air. So you don’t have to worry about where to put the individual comments. For example, the entire Swagger definition, tag definition, etc., can be written in any PHP file that can be scanned.

Brief description of common fields

Here is just a brief explanation of their own understanding and translation, more detailed field description, or to see the document. Bfanger. nl/swagger-exp…

Interface description (@SWgGET, @SWgPOST, etc.)

Summary - string Specifies the brief description of the interface, which is displayed on the interface header and cannot exceed 120 characters. Description-string Specifies details about the interface. Externaldocs-string Specifies the external document link operationId - String Identifies Consumes - [string] The MIME type that the interface receives produces - [string] The MIME types returned by the interface, for example, Application/JSON schemes - [string] Interface supported by the agreement, only values: "HTTP", "HTTPS", "ws" and "WSS parameters - [Parameter Object | Reference Object] argument listCopy the code

Parameter Description (@swgparameter) Common fields:

Name-string Specifies the parameter name. Pass the parameter by path (in value "path") Where the in-string argument comes from. Mandatory. The value can be "query", "header", "path", "formData", or "body" description-string Parameter description. Type-string Specifies the parameter type. Value: "string", "number", "INTEGER "," Boolean ", "array", "file" required - Boolean Specifies whether the parameter is mandatory. The value must be true if the parameter is passed by path (in: "path"). Default - * Default value. There are a lot of rules that I didn't use when you were going to pass parameters through path. Those of you who use it, look at the document.Copy the code

Problems encountered

Cross-domain problems:

The swagger cow X is that it can access the interface in place on the document and display the output, which is very convenient for debugging and interface. However, if you do not want swagger-UI deployed under the interface project, then when swagger-UI accesses the interface in place, the result will not be requested due to cross-domain problems. Response Body: no content). This is not about how to solve the cross-domain problem, just to give you an idea of the above problem, know that the error is caused by cross-domain, it is easy to solve.

Login authentication:

If you want to put documents on the public network, it is not appropriate to expose your own interface. Therefore, the access address of the document should be authenticated. Since the security requirements are not high and the development team is small, I directly used the HTTP Basic Auth provided by Nginx to do login authentication. Old rules, first stick official documents: www.nginx.com/resourc… This authentication is simple, it’s done in minutes. Without elaboration, there are two steps:

1. First run the htpasswd command to generate the account name and password for the student who needs to access the document:

$ htpasswd -cb your/path/to/api_project_accounts.db admin password_for_admin
$ htpasswd -b your/path/to/api_project_accounts.db liuxu 123456
$ htpasswd your/path/to/api_project_accounts.db xiaomingCopy the code

The -c option creates a new account file (api_project_accounts.db) if it does not exist. Therefore, you must add -c to the first account and do not add -c to subsequent accounts. The -b parameter indicates that the password is specified in plain text, which is the last input (password_for_admin, 123456) in the first and second commands. So if you don’t want to specify it in plain text, you can omit -b, which, like the third command above, will make you enter an invisible password, just like the sudo command. The above command creates three accounts, admin, liuxu, and xiaoming, and saves the account passwords in the api_project_accounts.db file.

2. Then enable HTTP Basic Auth for your own access address in the nginx project configuration.

location /dist {
    auth_basic              "my api project login";
    auth_basic_user_file    your/path/to/api_project_accounts.db;
}Copy the code

Restart nginx:

nginx -s reloadCopy the code

A simple login authentication is set up.

NOTE:

If you do not have the htpass command on your system, you can install the apache HTTPD server software, which contains the htpass command:

yum install httpdCopy the code

Reference:

[1] swagger 官网 [2] swagger project address [3] swagger UI project address [4] swagger PHP project address