SwaggerのPHP実装であるSwagger-PHPの使い方についてLaravelプロジェクトで確認し、Swaggerとはどんなものか調査したメモになります。

Swaggerの全体像については、「RESTful APIの記述標準化を担うSwaggerとは? | NTT Communications Developer Portal」が参考になりました。

Swaggerを利用したアプリケーション開発は、まずSwaggerドキュメントを作成して、サーバーのスタブとクライアントライブラリを生成し、APIロジックとクライアントUIなどを実装していくような流れになるといったところのようです。
Laravelはありませんが、コード生成ツール swagger-codegen には、Laravelの軽量版であるLumen用のサーバースタブは生成可能です。

この記事では、Laravel 5.2プロジェクトでAPIを作成して、Swagger-PHPのアノテーションからSwaggerドキュメントを生成してSwagger-UIでテストするまでを解説します。
つまり、APIの開発とSwaggerドキュメントの開発は平行して行っていく手順を想定しています。

前提条件

この記事内容を試すにあたって必要な準備は、以下のとおりです。

  • Laravelの動作するPHPバージョンがインストール済み
  • Composerがグローバルにインストール済み
  • Laravel 5.2プロジェクトを生成済み

Laravelの実行環境をすぐに作るにはVagrant Homesteadが便利です。
Laravel homesteadを利用したLaravel 5 ローカル開発環境の構築」も参考にしてください。

Laravel プロジェクトに Swagger-PHP をインストール

Swagger-PHPを、ComposerでLaravelプロジェクトに追加します。

$ composer require zircote/swagger-php

Swagger-PHPプロジェクトのルートディレクトリについて

PHPで、Swaggerドキュメントを生成するには、Swagger-PHPを利用します。

Swagger-PHPは、プロジェクトのソースに記述されたアノテーション(doctrine annotations)を読み込み、Swaggerフィアルを生成するツールです。

Swagger-PHPは、Swaggerとしてのプロジェクトルートはコマンドにて指定しますが、Laravel 5プロジェクトの場合は、app/Http/ControllersディレクトリをSwaggerプロジェクトのルートディレクトリとして扱うのが良さそうです。

APIを生成して、アノテーションを記述

Swagger-PHPのアノテーションの記述方法については、Getting startedに解説があります。
ここでは、この解説に沿ってProductモデルとProductのリストを返すAPIを実装し、それぞれにSwagger用のアノテーションを記述してみます。

APIの基本情報

まずは、APIの基本情報を記述します。
アノテーションは、Swaggerプロジェクトのパスに指定したディレクトリ内の任意のPHPファイルであればどこでも追加できます。
appディレクトリをプロジェクトパスとして、app直下にswagger.phpを作成して基本情報のアノテーションを追加してみました。

<?php

/**
 * @SWG\Swagger(
 *   schemes={"http"},
 *   host="homestead.app",
 *   basePath"="/api",
 *   @SWG\Info(
 *     title="My first swagger documented API",
 *     version="1.0.0"
 *   )
 * )
 */

モデルにスキーマ定義を記述

Productモデルを作成します。

$ php artisan make:model Product --migration
Model created successfully.
Created Migration: 2016_08_03_091651_create_products_table

マイグレーションの中身は省略します。

Productモデルのコードには、以下のように、アノテーションを追記します。
LaravelのEloquentは、通常フィールドは不要ですが、Swagger用に記述しています。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

/**
 * @SWG\Definition()
 */
class Product extends Model
{
  /**
   * The product id
   * @var integer
   *
   * @SWG\Property()
   */
  public $id;

  /**
   * The product name
   * @var string
   *
   * @SWG\Property()
   */
  public $name;
}

コントローラにAPI定義を記述

次に、コントローラーを作成します。
ここでは、コントローラを名前空間Apiの下に作成し、APIのパスも/api/productsにしてみます。

$ php artisan make:controller Api/ProductController
Controller created successfully.

作成したコントローラに、Productのリストを返す処理を実装して、Swaggerのアノテーションを記述します。

<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

use App\Product;

class ProductController extends Controller
{
  /**
   * @SWG\Get(
   *   path="/api/products",
   *   summary="list products",
   *   @SWG\Response(
   *     response=200,
   *     description="A list with products",
   *     @SWG\Schema(
   *             type="array",
   *             @SWG\Items(ref="#/definitions/Product")
   *     )
   *   ),
   *   @SWG\Response(
   *     response=500,
   *     description="an ""unexpected"" error"
   *   )
   * )
   */
  public function index()
  {
    return response()->json([
      'result'    => [
          'statistics' => [
              'products' => Product::all()
          ],
      ],
      'message'   => '',
      'type'      => 'success',
      'status'    => 0
    ]);
  }
}

@SWG\Responseには、@SWG\Schemaで作成したモデルProductを指定しています。

補足

ところで、Arrays and Objectsの説明では、21行目のresponseの値は”default”が指定されていますが、AWS API Gatewayでは”default”をサポートしていないので、エラーは500を返すようにしています。

Swagger-UIから動作確認をするために、作成したAPIのルーティングを追加して実際に動くようにしておきます。

Route::get('/api/products', 'Api\ProductController@index');

Swaggerドキュメントを生成する

Swaggerドキュメントは、以下のコマンドを実行すると生成されます。

$ mkdir public/api
$ ./vendor/bin/swagger ./app --output ./public/api/

以下の内容のswagger.jsonが生成されました。

{
    "swagger": "2.0",
    "info": {
        "title": "My first swagger documented API",
        "version": "1.0.0"
    },
    "host": "homestead.app",
    "basePath": "/api",
    "schemes": [
        "http"
    ],
    "paths": {
        "/products": {
            "get": {
                "summary": "list products",
                "responses": {
                    "200": {
                        "description": "A list with products",
                        "schema": {
                            "type": "array",
                            "items": {
                                "$ref": "#/definitions/Product"
                            }
                        }
                    },
                    "500": {
                        "description": "an \"unexpected\" error"
                    }
                }
            }
        }
    },
    "definitions": {
        "Product": {
            "properties": {
                "id": {
                    "description": "The product id",
                    "type": "integer"
                },
                "name": {
                    "description": "The product name",
                    "type": "string"
                }
            }
        }
    }
}

Swagger UIのインストール

swagger-api/swagger-uiをダウンロードして展開し、distディレクトリをLaravelプロジェクトのpublic/swaggerディレクトリにコピーします。

$ cp -r ~/Downloads/swagger-ui-master/dist public
$ mv public/dist public/swagger

ホスト名homestead.appの場合、ブラウザでhttp://homestead.app/swagger/index.htmlを開き、http://homestead.app/api/swagger.jsonを入力して[Explore]をクリックすると、生成されたAPIを表示できます。
[Try it out!]をクリックすると実際にAPIにアクセスし結果を確認できます。

swagger-getting-start-using-swagger-ui
productsテーブルには、幾つかデータを入れた状態でテストしています。

まとめ

Laravel5とSwagger-PHPを組み合わせたAPIドキュメントの生成手順について確認してきましたが、実際にプロジェクトで使うには以下の様なことを考える必要がありそうです。

  • APIのバーション管理などを考えると、APIのモデルとEloquentモデルは分けたほうがよさそう
  • Swagger-PHPアノテーションの記述量が多いのと、アノテーションのためのコード量が必要になるのがつらい
  • LaravelでのAPI実装は、dingo/apiを利用した方が良いので、dingo/apiとSwagger-PHPのプロジェクト構成にしたい
  • Swaggervel(補足参照)のような、Laravel5とSwagger-PHPを統合するパッケージの検討
  • AWS Gateway APIやAzure API ManagementなどGatewayサービスとの統合

補足: Swaggervel

LaravelプロジェクトにSwagger-PHPとSwagger UIを統合できるパッケージとしては、Swaggervelをはじめ、いくつかパッケージがあるようです。

Swaggervelについても動作を確認してみたので、メモしておきます。
ただし、すんなりは動かなかったのであまりおすすめはできません。

Laravel 5の場合は2.0ブランチを利用する。
slampenny/Swaggervel

$ composer require jlapp/swaggervel:2.0.x-dev

しかし、このブランチではapi-docsでエラーが発生してしまう。
Class ‘Swagger\Swagger’ not found #43
によるとmaster-devだと動くらしい。

$ composer require jlapp/swaggervel:master-dev

Jlapp\Swaggervel\SwaggervelServiceProviderをapp/config/app.phpのprovidersに追加

    'providers' => [
        ...
        Jlapp\Swaggervel\SwaggervelServiceProvider::class,
    ],

artisan vendor:publishコマンドを実行すると、設定ファイルとSwagger-UIのリソースがコピーされます。

$ php artisan vendor:publish
Copied File [/vendor/jlapp/swaggervel/src/config/swaggervel.php] To [/config/swaggervel.php]
Copied Directory [/vendor/jlapp/swaggervel/public] To [/public/vendor/swaggervel]
Copied Directory [/vendor/jlapp/swaggervel/src/views] To [/resources/views/vendor/swaggervel]
Publishing complete for tag []!

Swaggerドキュメントを生成します。
出力先は、storage/docsディレクトリを指定します。

$ mkdir storage/docs
$ ./vendor/bin/swagger app/Http/Controllers -o storage/docs/

Swagger-UIのパスは、/api/docsです(masterはREADMEと異なるので注意)。
Swaggerファイルのパス/docがセットされた状態で開きます。
swagger-getting-swagger-ui-with-swaggervel
Swaggervelに含まれるSwagger-UITは古そうですね。

Swagger-PHPとSwagger-UI一つのパッケージにまとめているだけなので、ドキュメントの通りに動かないこと、Swagger-UIが古いことなどを考えると、それぞれ個別に管理するようにした方が良いかもしれません。

参考記事

Integrate Swagger into Laravel
LaravelプロジェクトのAPIをswaggerを使ってドキュメント化
Getting started with Swagger and Swaggervel

次にやること

この内容を踏まえ、次にAWS Gateway APIとMicrosoftのAPI ManagementにおけるSwaggerインポートについて調べてみる予定です。