swagger & swagger-php & swagger-ui
swagger-phpを使ってswagger形式のAPIドキュメントを作成し、swagger-uiを使って参照・実行したい。
(概要、セットアップ等は以前の内容を参照ください。。)
swagger-phpで、swagger2.0に対応したドキュメントは下記の2つくらい??
ちょっと試行錯誤しながらの部分もあったのでメモとして残しておきます。。
環境
-
FuelPHP
-
APIのURLは
192.168.99.100:81
-
ユーザー
とタグ
というリソースを扱うAPIで、エンドポイントはユーザ関連のCRUDをひととおり作るイメージ
アノテーションサンプル
コントローラ
API全体の定義と、各エンドポイントの定義を記述していく。
基底クラスと各コントローラに別れるので、それぞれに記述する。
- コントローラの基底クラス
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の雰囲気だけ。。
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モデルの定義に使用)
app/classes/resource/base.php
use Swagger\Annotations as SWG;
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;
}
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がこちら
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"
* )