CakePHP4用のOpenApi bake theme pluginを公開しました

最近は久々にガッツリPHPのコードを書いているわたなべです。

このところ、仕事でもプライベートでもPHPAPIを書いて、Next.jsでフロントのWebアプリを書くことがほとんどです。

この場合API仕様は以前ブログにも書きましたが、swagger-phpアノテーションで記述して、Swagger-UIで参照できる様にしています。

kaz29.hatenablog.com

Swagger-UI と swagger-php

最近は使われている方も多いと思いますが、簡単に説明すると、EntityとControllerに以下の様なアノテーションを記述します。

Entity/Article.php

/**
 * Article Entity
 *
 * @OA\Schema(
 *      schema="Article",
 *      title="",
 *      description="Article entity",
 *       @OA\Property(
 *           property="id",
 *           type="integer",
 *           format="int32",
 *           description="",
 *       ),
 *       @OA\Property(
 *           property="user_id",
 *           type="integer",
 *           format="int32",
 *           description="",
 *       ),
 *       @OA\Property(
 *           property="title",
 *           type="string",
 *           description="",
 *       ),
 *       @OA\Property(
 *           property="slug",
 *           type="string",
 *           description="",
 *       ),
 *       @OA\Property(
 *           property="body",
 *           type="string",
 *           description="",
 *       ),
 *       @OA\Property(
 *           property="published",
 *           type="boolean",
 *           description="",
 *       ),
 *       @OA\Property(
 *           property="created",
 *           type="string",
 *           format="datetime",
 *           description="",
 *       ),
 *       @OA\Property(
 *           property="modified",
 *           type="string",
 *           format="datetime",
 *           description="",
 *       ),
 * )

Controller/Api/ArticlesController.php

    /**
     * Index method
     *
     * @OA\Get(
     *     path="/api/articles.json",
     *     summary="Articles index",
     *     description="Articles index",
     *     @OA\Parameter(
     *         name="page",
     *         in="query",
     *         required=false,
     *         @OA\Schema(
     *             type="number",
     *         ),
     *         description=""
     *     ),
     *     @OA\Parameter(
     *         name="limit",
     *         in="query",
     *         required=false,
     *         @OA\Schema(
     *             type="number",
     *         ),
     *         description=""
     *     ),
     *     @OA\Response(
     *         response=200,
     *         description="successful operation",
     *         @OA\JsonContent(
     *              @OA\Property(
     *                  property="success",
     *                  type="boolean",
     *                  default=true,
     *              ),
     *              @OA\Property(
     *                  property="data",
     *                  type="array",
     *                  @OA\Items(
     *                      allOf={
     *                          @OA\Schema(ref="#/components/schemas/Article"),
     *                          @OA\Schema(
     *                              @OA\Property(
     *                                  property="user",
     *                                  ref="#/components/schemas/User",
     *                                  description="User Entity",
     *                              ),
     *                          ),
     *                      },
     *                  ),
     *              ),
     *              @OA\Property(
     *                  property="pagination",
     *                  ref="#/components/schemas/Pagination",
     *              ),
     *         ),
     *     ),
     * )
     * @return \Psr\Http\Message\ResponseInterface
     */

これらのコードを、以下の様なコマンドでswagger-phpを使用してビルドします。

swagger.jsonをビルドするコマンド

#!/usr/local/bin/php -q
<?php
include_once __DIR__.'/../autoload.php';

$app_path = '.';
$openapi = \OpenApi\scan(
    $app_path, 
    [
        'exclude' => [
            'vendor', 
            'tmp', 
            'logs', 
            'tests', 
            'webroot',
        ]
    ]
);
file_put_contents(dirname($app_path).'/docs/swagger.json', $openapi->toJson());

ビルドが成功すると、swagger.jsonが作成されるのでこれをSwagger-UIで読み込むと、以下の様にドキュメントを見ることはもちろん、Swagger-UI上からAPIを呼び出すこともできます。

f:id:kaz_29:20210306062925p:plain
Swagger-UI サンプル

これすごい便利なのでおすすめなのですが、記述するのが結構面倒なのと、記述方法にいろいろ癖があるので書くたびに毎回試行錯誤することになったりします。

前からなんとかしたいなぁと思っていたのですが、現在とあるリプレース案件で大量にAPIを作成する予定で、この作業を少しでも効率化したいと思いCakePHPのbakeテンプレートを書きました。

bakeテンプレートを自作すると、CakePHPを使っている方であればご存知のbakeコマンドで生成される雛形のソースコードをカスタマイズすることができます。

github.com

OpenApiTheme plugin

OpenApiTheme pluginでは、APIを作成する際には定番のfriends od cake CRUD Pluginを使うことを前提で作成しました。

今回は、以下の2つのbakeコマンドを追加しています。

  • open_api_model - モデルのbake時にEntityにOpenApiのSchema定義を自動生成する
  • open_api_controller - コントローラのbake時にCRUDAPI定義を自動生成する

実際には以下のような感じでbakeすることができます。

// モデルのbake
$ bin/cake bake open_api_model Articles

// コントローラのbake
$ bin/cake bake open_api_controller Articles --prefix Api

現在のバージョンでは、EntityのSchameにはアソシエーション先のプロパティはあえて含めないようになっています。 定義すると便利は便利なのですが、実際の利用シーンではどのアソシエーションをContainさせるかはAPIによって変わるケースが多いのでEntity側で定義してしまうと使いにくいことが多いです。 この為、OpenApiTheme pluginではEntityのSchameにはアソシエーションを含めずにControllerのAPI定義の方で複数のSchemaを合成(?)するようにしています。

公式のbakeでは、index actionではBelongsToのみcontainし、view acrionでは全てのアソシエーションをcontainするコードが生成されるので、それに倣って以下のようなレスポンスを定義しています。

index action のレスポンス定義サンプル

     *     @OA\Response(
     *         response=200,
     *         description="successful operation",
     *         @OA\JsonContent(
     *              @OA\Property(
     *                  property="success",
     *                  type="boolean",
     *                  default=true,
     *              ),
     *              @OA\Property(
     *                  property="data",
     *                  type="array",
     *                  @OA\Items(
     *                      allOf={
     *                          @OA\Schema(ref="#/components/schemas/Article"),
     *                          @OA\Schema(
     *                              @OA\Property(
     *                                  property="user",
     *                                  ref="#/components/schemas/User",
     *                                  description="User Entity",
     *                              ),
     *                          ),
     *                      },
     *                  ),
     *              ),
     *              @OA\Property(
     *                  property="pagination",
     *                  ref="#/components/schemas/Pagination",
     *              ),
     *         ),
     *     ),

view action のレスポンス定義サンプル

     *     @OA\Response(
     *         response=200,
     *         description="successful operation",
     *         @OA\JsonContent(
     *              @OA\Property(
     *                  property="success",
     *                  type="boolean",
     *                  default=true,
     *              ),
     *              @OA\Property(
     *                  property="data",
     *                  allOf={
     *                      @OA\Schema(ref="#/components/schemas/Article"),
     *                      @OA\Schema(
     *                          @OA\Property(
     *                              property="user",
     *                              ref="#/components/schemas/User",
     *                              description="User Entity",
     *                          ),
     *                          @OA\Property(
     *                              property="tags",
     *                              type="array",
     *                              @OA\Items(ref="#/components/schemas/Tag"),
     *                              description="Tag Entities",
     *                          ),
     *                      ),
     *                  },
     *              ),
     *         ),
     *     ),

bakeしたままでは実際に作成したいAPIにマッチしないケースも多々あるとは思いますが、これを元に実際のAPI定義を作成することで、記述の手間をだいぶ軽減できると思います。

以下で実際にOpenApiTheme pluginで生成したAPI仕様を確認できますので、ぜひ一度見てみてください。

petstore.swagger.io

開発環境でのSwagger-UIの利用

普段利用している開発環境では、開発中のAPIをSwagger-UIから直接叩けるように、開発環境用のdocker-compose.ymlにSwagger-UIのコンテナも含めるようにしています。

docker hub に上がっている、公式のDockerコンテナを利用しています。

まとめ

現在進行中の実案件にもOpenApiTheme pluginを導入して使い始めていますが、仕様書作成がだいぶ捗ります。

随時フィードバックして改善していくつもりですが、ぜひ使っていただいて、要望などあればIssueなりPRなりいただければと思います。

github.com