Swagger-PHPでSwagger2.0に対応したアノテーションサンプル

# swagger & swagger-php & swagger-ui

swagger-phpを使ってswagger形式のAPIドキュメントを作成し、swagger-uiを使って参照・実行したい。
（概要、セットアップ等は[以前の内容](http://qiita.com/teratsyk/items/40fc5d8bf58cb2e5002e)を参照ください。。）

swagger-phpで、swagger2.0に対応したドキュメントは下記の２つくらい？？

* [swagger-php/Examples](https://github.com/zircote/swagger-php/tree/master/Examples)
* [swagger-explained](http://bfanger.github.io/swagger-explained/)

ちょっと試行錯誤しながらの部分もあったのでメモとして残しておきます。。


# 環境

* FuelPHP

* APIのURLは`192.168.99.100:81`

* `ユーザー`と`タグ`というリソースを扱うAPIで、エンドポイントはユーザ関連のCRUDをひととおり作るイメージ

# アノテーションサンプル

### コントローラ

API全体の定義と、各エンドポイントの定義を記述していく。
基底クラスと各コントローラに別れるので、それぞれに記述する。

* コントローラの基底クラス

```shell-session:app/classes/controller/base.php
use Swagger\Annotations as SWG;

/**
 * @SWG\Swagger(
 *   schemes={"http"},
 *   host="192.168.99.100:81",
 *   basePath="",
 *   @SWG\Info(
 *     version="1.0.0",
 *     title="sample API",
 *     description="サンプルAPIでswaggerを試す",
 *     @SWG\Contact(
 *       email=""
 *     ),
 *     @SWG\License(
 *       name="",
 *       url=""
 *     )
 *   ),
 *   @SWG\ExternalDocumentation(
 *     description="Find out more about Swagger",
 *     url="http://swagger.io"
 *   )
 * )
 *
 * タグ（エンドポイントのグルーピング）
 * @SWG\Tag(
 *   name="user",
 *   description="ユーザー関連"
 * )
 * @SWG\Tag(
 *   name="tag",
 *   description="タグ関連"
 * )
 *
 * 認証関連
 * @SWG\SecurityScheme(
 *   securityDefinition="api_key",
 *   type="apiKey",
 *   in="header",
 *   name="api_key"
 * )
 *
 * API全体で共通なクエリパラメータ
 * @SWG\Parameter(
 *     name="order",
 *     description="取得順序",
 *     in="query",
 *     required=false,
 *     type="string"
 * )
 * @SWG\Parameter(
 *     name="limit",
 *     description="取得件数",
 *     in="query",
 *     required=false,
 *     type="integer",
 *     format="int32"
 * )
 * @SWG\Parameter(
 *     name="offset",
 *     description="取得開始位置",
 *     in="query",
 *     required=false,
 *     type="integer",
 *     format="int32"
 * )
 *
 * 共通レスポンスとしてここに定義して後から呼び出せるかと思ったらダメだった。。
 * @SWG\Response(response=200, description="OK")
 * @SWG\Response(response=201, description="Created")
 * @SWG\Response(response=202, description="Accepted")
 * @SWG\Response(response=204, description="No Content")
 * @SWG\Response(response=207, description="Multi-Status")
 * @SWG\Response(response=304, description="Not Modified")
 * @SWG\Response(response=400, description="Bad Request")
 * @SWG\Response(response=401, description="Unauthorized")
 * @SWG\Response(response=403, description="Forbidden")
 * @SWG\Response(response=405, description="Method Not Allowed")
 * @SWG\Response(response=500, description="Internal Server Error")
 * @SWG\Response(response=503, description="Service Unavailable")
 */
class Controller_Base extends Controller_Rest
{
}
 ```

 * ユーザーコントローラ
   * とりあえずgetとpostの雰囲気だけ。。

```shell-session:app/classes/controller/user.php
class Controller_User extends Controller_Base
{
	 /**
	 * @SWG\Get(
	 *   path="/users",
	 *   summary="ユーザ一覧",
	 *   tags={"user"},
	 *   description="ユーザの一覧を取得する",
	 *   consumes={"application/json", "application/xml"},
	 *   produces={"application/json", "application/xml"},
	 *   @SWG\Parameter(ref="#/parameters/order"),
	 *   @SWG\Parameter(ref="#/parameters/limit"),
	 *   @SWG\Parameter(ref="#/parameters/offset"),
	 *   @SWG\Parameter(
	 *     name="id",
	 *     in="query",
	 *     description="Id指定（CSV複数可能）",
	 *     required=false,
	 *     type="array",
	 *     @SWG\Items(type="integer", format="int32"),
	 *     collectionFormat="csv"
	 *   ),
	 *   @SWG\Parameter(
	 *     name="status",
	 *     in="query",
	 *     description="ステータス指定",
	 *     required=false,
	 *     type="string",
     *     enum={"available", "pending", "sold"}
	 *   ),
	 *   @SWG\Response(
	 *     response=200,
	 *     description="OK",
	 *     @SWG\Schema(
	 *       type="array",
	 *       @SWG\Items(ref="#/definitions/User")
	 *     )
	 *   ),
	 *   @SWG\Response(response=400, description="Bad Request"),
	 *   @SWG\Response(response=500, description="Internal Server Error")
	 * )
	 */
	public function get_list()
	{
	}

	/**
	 * @SWG\Post(
	 *   path="/users",
	 *   summary="ユーザ作成",
	 *   tags={"user"},
	 *   description="ユーザ作成",
     *   @SWG\Parameter(
     *     in="body",
     *     name="body",
     *     description="登録するユーザデータ",
     *     required=true,
     *     @SWG\Schema(
	 *       type="array",
	 *       @SWG\Items(ref="#/definitions/User")
     *     )
     *   ),
	 *   @SWG\Response(
	 *     response=201,
	 *     description="Created",
	 *     @SWG\Schema(
	 *       type="array",
	 *       @SWG\Items(ref="#/definitions/User")
	 *     )
	 *   ),
	 *   @SWG\Response(
	 *     response=207,
	 *     description="Multi-Status",
	 *     @SWG\Schema(
	 *       type="object",
	 *       @SWG\Property(property="error_code", type="integer", format="int32"),
	 *       @SWG\Property(property="error_message", type="string")
	 *     )
	 *   ),
	 *   @SWG\Response(response=400, description="Bad Request"),
	 *   @SWG\Response(response=500, description="Internal Server Error")
	 * )
	 */
	public function post_list()
	{
	}

	/**
	 * @SWG\Get(
	 *   path="/users/{id}",
	 *   summary="ユーザ詳細",
	 *   tags={"user"},
	 *   description="ユーザの詳細情報を取得する",
	 *   consumes={"application/json", "application/xml"},
	 *   produces={"application/json", "application/xml"},
	 *   @SWG\Parameter(
	 *     name="id",
	 *     in="path",
	 *     description="ユーザID",
	 *     required=true,
	 *     type="integer",
	 *     format="int32",
	 *   ),
	 *   @SWG\Response(
	 *     response=200,
	 *     description="OK",
	 *     @SWG\Schema(ref="#/definitions/User")
	 *   ),
	 *   @SWG\Response(response=400, description="Bad Request"),
	 *   @SWG\Response(response=500, description="Internal Server Error")
	 * )
	 */
	public function get_detail($id)
	{
	}

	/**
	 * @SWG\Put(
	 *   path="/users/{id}",
	 *   summary="update user",
	 *   tags={"user"},
	 *   description="update user",
	 *   @SWG\Parameter(
	 *     name="id",
	 *     in="path",
	 *     description="ユーザID",
	 *     required=true,
	 *     type="integer",
	 *     format="int32",
	 *   ),
     *   @SWG\Parameter(
     *     in="body",
     *     name="body",
     *     description="置換更新するユーザデータ",
     *     required=true,
     *     @SWG\Schema(ref="#/definitions/User")
     *   ),
	 *   @SWG\Response(
	 *     response=201,
	 *     description="Created",
	 *     @SWG\Schema(ref="#/definitions/User")
	 *   ),
	 *   @SWG\Response(response=400, description="Bad Request"),
	 *   @SWG\Response(response=500, description="Internal Server Error")
	 * )
	 */
	public function put_detail($id)
	{
	}

	/**
	 * @SWG\Patch(
	 *   path="/users/{id}",
	 *   summary="patch user",
	 *   tags={"user"},
	 *   description="patch user",
	 *   @SWG\Parameter(
	 *     name="id",
	 *     in="path",
	 *     description="ユーザID",
	 *     required=true,
	 *     type="integer",
	 *     format="int32",
	 *   ),
     *   @SWG\Parameter(
     *     in="body",
     *     name="body",
     *     description="部分更新するユーザデータ",
     *     required=true,
     *     @SWG\Schema(ref="#/definitions/User")
     *   ),
	 *   @SWG\Response(
	 *     response=201,
	 *     description="Created",
	 *     @SWG\Schema(ref="#/definitions/User")
	 *   ),
	 *   @SWG\Response(response=400, description="Bad Request"),
	 *   @SWG\Response(response=500, description="Internal Server Error")
	 * )
	 */
	public function patch_detail($id)
	{
	}

	/**
	 * @SWG\Delete(
	 *   path="/users/{id}",
	 *   summary="delete user",
	 *   tags={"user"},
	 *   description="delete user",
	 *   @SWG\Parameter(
	 *     name="id",
	 *     in="path",
	 *     description="ユーザID",
	 *     required=true,
	 *     type="integer",
	 *     format="int32",
	 *   ),
	 *   @SWG\Response(
	 *     response=204,
	 *     description="No Content"
	 *   ),
	 *   @SWG\Response(response=400, description="Bad Request"),
	 *   @SWG\Response(response=500, description="Internal Server Error")
	 * )
	 */
	public function delete_detail($id)
	{
	}
}
```

### リソース

今回のAPIでは、APIであつかうデータを`fuel/app/classes/resource`内にクラス定義することにした。
（`fuel/app/classes/model`は通常どおりDBモデルの定義に使用）

```shell-session:app/classes/resource/base.php
use Swagger\Annotations as SWG;
```

```shell-session:app/classes/resource/user.php
/**
 * @SWG\Definition(definition="User", type="object")
 */
class Resource_User extends Resource_Base
{
	/**
	 * @SWG\Property(property="id", type="integer", format="int32")
	 * @var int
	 */
	public $id;

	/**
	 * @SWG\Property(property="name", type="integer", format="int32")
	 * @var string
	 */
	public $name;

	/**
	 * @SWG\Property(property="status", type="string", enum={"available", "pending", "sold"})
	 * @var string
	 */
	public $status;

	/**
	 * @SWG\Property(property="tags", type="array", @SWG\Items(ref="#/definitions/Tag"))
	 * @var Tag[]
	 */
	public $tags;
}
```

```shell-session:app/classes/resource/tag.php
/**
 * @SWG\Definition(definition="Tag", type="object")
 */
class Resource_Tag extends Resource_Base
{
	/**
	 * @SWG\Property(property="id", type="integer", format="int32")
	 * @var int
	*/
	public $id;

	/**
	 * @SWG\Property(property="tag", type="string")
	 * @var string
	 */
	public $tag;
}
```

# JSON出力

出力されたJSONがこちら

```shell-session:swagger.json
{
    "swagger": "2.0",
    "info": {
        "title": "sample API",
        "description": "\u30b5\u30f3\u30d7\u30ebAPI\u3067swagger\u3092\u8a66\u3059",
        "contact": {
            "email": ""
        },
        "license": {
            "name": "",
            "url": ""
        },
        "version": "1.0.0"
    },
    "host": "192.168.99.100:81",
    "basePath": "",
    "schemes": [
        "http"
    ],
    "paths": {
        "/users": {
            "get": {
                "tags": [
                    "user"
                ],
                "summary": "\u30e6\u30fc\u30b6\u4e00\u89a7",
                "description": "\u30e6\u30fc\u30b6\u306e\u4e00\u89a7\u3092\u53d6\u5f97\u3059\u308b",
                "consumes": [
                    "application/json",
                    "application/xml"
                ],
                "produces": [
                    "application/json",
                    "application/xml"
                ],
                "parameters": [
                    {
                        "$ref": "#/parameters/order"
                    },
                    {
                        "$ref": "#/parameters/limit"
                    },
                    {
                        "$ref": "#/parameters/offset"
                    },
                    {
                        "name": "id",
                        "in": "query",
                        "description": "Id\u6307\u5b9a\uff08CSV\u8907\u6570\u53ef\u80fd\uff09",
                        "required": false,
                        "type": "array",
                        "items": {
                            "type": "integer",
                            "format": "int32"
                        },
                        "collectionFormat": "csv"
                    },
                    {
                        "name": "status",
                        "in": "query",
                        "description": "\u30b9\u30c6\u30fc\u30bf\u30b9\u6307\u5b9a",
                        "required": false,
                        "type": "string",
                        "enum": [
                            "available",
                            "pending",
                            "sold"
                        ]
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/User"
                            }
                        }
                    },
                    "400": {
                        "description": "Bad Request"
                    },
                    "500": {
                        "description": "Internal Server Error"
                    }
                }
            },
            "post": {
                "tags": [
                    "user"
                ],
                "summary": "\u30e6\u30fc\u30b6\u4f5c\u6210",
                "description": "\u30e6\u30fc\u30b6\u4f5c\u6210",
                "parameters": [
                    {
                        "name": "body",
                        "in": "body",
                        "description": "\u767b\u9332\u3059\u308b\u30e6\u30fc\u30b6\u30c7\u30fc\u30bf",
                        "required": true,
                        "schema": {
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/User"
                            }
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Created",
                        "schema": {
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/User"
                            }
                        }
                    },
                    "207": {
                        "description": "Multi-Status",
                        "schema": {
                            "properties": {
                                "error_code": {
                                    "type": "integer",
                                    "format": "int32"
                                },
                                "error_message": {
                                    "type": "string"
                                }
                            },
                            "type": "object"
                        }
                    },
                    "400": {
                        "description": "Bad Request"
                    },
                    "500": {
                        "description": "Internal Server Error"
                    }
                }
            }
        },
        "/users/{id}": {
            "get": {
                "tags": [
                    "user"
                ],
                "summary": "\u30e6\u30fc\u30b6\u8a73\u7d30",
                "description": "\u30e6\u30fc\u30b6\u306e\u8a73\u7d30\u60c5\u5831\u3092\u53d6\u5f97\u3059\u308b",
                "consumes": [
                    "application/json",
                    "application/xml"
                ],
                "produces": [
                    "application/json",
                    "application/xml"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "\u30e6\u30fc\u30b6ID",
                        "required": true,
                        "type": "integer",
                        "format": "int32"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "schema": {
                            "$ref": "#/definitions/User"
                        }
                    },
                    "400": {
                        "description": "Bad Request"
                    },
                    "500": {
                        "description": "Internal Server Error"
                    }
                }
            },
            "put": {
                "tags": [
                    "user"
                ],
                "summary": "update user",
                "description": "update user",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "\u30e6\u30fc\u30b6ID",
                        "required": true,
                        "type": "integer",
                        "format": "int32"
                    },
                    {
                        "name": "body",
                        "in": "body",
                        "description": "\u7f6e\u63db\u66f4\u65b0\u3059\u308b\u30e6\u30fc\u30b6\u30c7\u30fc\u30bf",
                        "required": true,
                        "schema": {
                            "items": {
                                "$ref": "#/definitions/User"
                            }
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Created",
                        "schema": {
                            "$ref": "#/definitions/User"
                        }
                    },
                    "400": {
                        "description": "Bad Request"
                    },
                    "500": {
                        "description": "Internal Server Error"
                    }
                }
            },
            "delete": {
                "tags": [
                    "user"
                ],
                "summary": "delete user",
                "description": "delete user",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "\u30e6\u30fc\u30b6ID",
                        "required": true,
                        "type": "integer",
                        "format": "int32"
                    }
                ],
                "responses": {
                    "204": {
                        "description": "No Content"
                    },
                    "400": {
                        "description": "Bad Request"
                    },
                    "500": {
                        "description": "Internal Server Error"
                    }
                }
            },
            "patch": {
                "tags": [
                    "user"
                ],
                "summary": "patch user",
                "description": "patch user",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "\u30e6\u30fc\u30b6ID",
                        "required": true,
                        "type": "integer",
                        "format": "int32"
                    },
                    {
                        "name": "body",
                        "in": "body",
                        "description": "\u90e8\u5206\u66f4\u65b0\u3059\u308b\u30e6\u30fc\u30b6\u30c7\u30fc\u30bf",
                        "required": true,
                        "schema": {
                            "items": {
                                "$ref": "#/definitions/User"
                            }
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "Created",
                        "schema": {
                            "$ref": "#/definitions/User"
                        }
                    },
                    "400": {
                        "description": "Bad Request"
                    },
                    "500": {
                        "description": "Internal Server Error"
                    }
                }
            }
        }
    },
    "definitions": {
        "Tag": {
            "properties": {
                "id": {
                    "type": "integer",
                    "format": "int32"
                },
                "tag": {
                    "type": "string"
                }
            },
            "type": "object"
        },
        "User": {
            "properties": {
                "id": {
                    "type": "integer",
                    "format": "int32"
                },
                "name": {
                    "type": "integer",
                    "format": "int32"
                },
                "status": {
                    "type": "string",
                    "enum": [
                        "available",
                        "pending",
                        "sold"
                    ]
                },
                "tags": {
                    "type": "array",
                    "items": {
                        "$ref": "#/definitions/Tag"
                    }
                }
            },
            "type": "object"
        }
    },
    "parameters": {
        "order": {
            "name": "order",
            "in": "query",
            "description": "\u53d6\u5f97\u9806\u5e8f",
            "required": false,
            "type": "string"
        },
        "limit": {
            "name": "limit",
            "in": "query",
            "description": "\u53d6\u5f97\u4ef6\u6570",
            "required": false,
            "type": "integer",
            "format": "int32"
        },
        "offset": {
            "name": "offset",
            "in": "query",
            "description": "\u53d6\u5f97\u958b\u59cb\u4f4d\u7f6e",
            "required": false,
            "type": "integer",
            "format": "int32"
        }
    },
    "responses": {
        "200": {
            "description": "OK"
        },
        "201": {
            "description": "Created"
        },
        "202": {
            "description": "Accepted"
        },
        "204": {
            "description": "No Content"
        },
        "207": {
            "description": "Multi-Status"
        },
        "304": {
            "description": "Not Modified"
        },
        "400": {
            "description": "Bad Request"
        },
        "401": {
            "description": "Unauthorized"
        },
        "403": {
            "description": "Forbidden"
        },
        "405": {
            "description": "Method Not Allowed"
        },
        "500": {
            "description": "Internal Server Error"
        },
        "503": {
            "description": "Service Unavailable"
        }
    },
    "securityDefinitions": {
        "api_key": {
            "type": "apiKey",
            "name": "api_key",
            "in": "header"
        }
    },
    "tags": [
        {
            "name": "user",
            "description": "\u30e6\u30fc\u30b6\u30fc\u95a2\u9023"
        },
        {
            "name": "tag",
            "description": "\u30bf\u30b0\u95a2\u9023"
        }
    ],
    "externalDocs": {
        "description": "Find out more about Swagger",
        "url": "http://swagger.io"
    }
}
```

これをswagger-uiに読み込ませると、クエリパラメータやリクエストボディ、レスポンスの定義も表示されるかと思います。

---

（追記）

* 日付

```
 *   @SWG\Parameter(
 *     name="id",
 *     in="path",
 *     description="ユーザID",
 *     required=true,
 *     type="string",
 *     format="date"
 *   )
```

* 日時

```
 *   @SWG\Parameter(
 *     name="id",
 *     in="path",
 *     description="ユーザID",
 *     required=true,
 *     type="string",
 *     format="date-time"
 *   )
```