OpenAPI generatorとlaravelの組み合わせでインターフェースをいい感じに守る
概要
OpenAPI generator と laravel の組み合わせで
laravel の自由度を残しながらいい感じにインターフェースを守れたので
generator の設定とかをメモとして残しておきます。
バージョン
Laravel 8.55.0
OpenAPI 3.0.0
ディレクトリ構成
├── open_api_generator
│ ├── api.yaml
│ ├── config_php.json
│ └── generator_php.sh
└── project
├── app
│ ├── Http
│ │ └── Controllers
│ │ ├── Api
│ │ │ └── Master
│ │ │ └── JobCategoryController.php
│ │ └── Controller.php
│ ├── Libs
│ │ └── OpenAPIUtility.php
│ ├── Models
│ │ └── JobCategory.php
└── routes
└── api.php
OpenAPI
generator 都合でクエリパラメータ、リクエストボディ、レスポンス等はモデル化してあります。
openapi: 3.0.0
info:
title: OpenAPI Tutorial
description: OpenAPI Tutorial by halhorn
version: 0.0.0
servers:
- url: http://localhost:80/api
description: 開発用
paths:
/job_categories:
get:
tags:
- "職種"
summary: 一覧取得
description: 詳細内容
parameters:
- in: query
name: name
schema:
type: string
description: 名称
required: false
- in: query
name: content
schema:
type: string
description: 内容
required: false
responses:
"200":
description: 職種一覧
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/jobCategory"
components:
schemas:
queryJobCategoryList:
description: クエリパラメータ 職種一覧
type: object
properties:
name:
type: string
description: 名称
content:
type: string
description: 内容
jobCategory:
description: レスポンス 職種
type: object
properties:
id:
type: integer
description: id
name:
type: string
description: 名称
content:
type: string
description: 内容
image:
type: string
description: 画像URL
sortNo:
type: integer
description: 並び順
createdAt:
type: string
format: date
description: 作成日時
updatedAt:
type: string
format: date
description: 更新日時
OpenAPI generator
ジェネレーターには laravel 用ではなく php 用を使います。
laravel 用はルートとコントローラに関するコードのみ生成されるだけでリクエスト、レスポンスの内容が oas に沿っているかどうかを保障するコードは生成されません。
また、コントローラとルートは手で書き換えたい部分でもあり、極力自動生成されたコードには手を加えたくないです。
laravel 用は使いにくいですね。。。
比べて php 用はリクエスト、レスポンスを oas に沿わせるコードが生成されるので、それを laravel に埋め込んで使っていきます。
{
"invokerPackage": "App\\OpenAPI",
"variableNamingConvention": "camel_case"
}
WORK_PATH='/out/php'
OUTPUT_PATH='../project/app/OpenAPI'
# コード生成
docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli generate -i /local/api.yaml -g php -o /local${WORK_PATH} -c /local/config_php.json
# enum型の定数名の頭に勝手につく文字を削除
grep -l 'NUMBER_' .${WORK_PATH}/lib/Model/* | xargs sed -i 's/NUMBER_//g'
# laravel側にコピー
mkdir -p ${OUTPUT_PATH}
cp -r .${WORK_PATH}/lib/* ${OUTPUT_PATH}
cp api.yaml ${OUTPUT_PATH}
# 作業ディレクトリ削除
rm -rf ./out
スクリプトを実行すると laravel 側にコードが生成されます。
Laravel
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
// model
use App\Models\JobCategory;
// request
use App\Http\Requests\Master\JobCategoryListRequest;
// openapi
use App\OpenAPI;
use App\Libs\OpenAPIUtility;
class JobCategoryController extends Controller
{
/**
* 職種 一覧取得
*
* @param Request $request
* @return JsonResponse
*/
public function list(Request $request): JsonResponse
{
// oasのリクエストモデルに変換
$parameters = new OpenAPI\Model\QueryJobCategoryList($request->all());
// メインロジック
$name = $parameters->getName();
$content = $parameters->getContent();
$items = JobCategory::when(isset($name), function ($query) use ($name) {
return $query->where('name', 'like', "%$name%");
})->when(isset($content), function ($query) use ($content) {
return $query->where('content', 'like', "%$content%");
})->orderBy('sort_no')->get()->toArray();
# oasのレスポンスモデルに変換して返す。
return response()->json(
OpenAPIUtility::dicstionariesToModelContainers(OpenAPI\Model\JobCategory::class, $items),
Response::HTTP_OK
);
}
}
やってる事はコントローラーに入ってくるリクエストと、レスポンスを oas に沿わせるようにしただけ。
この形なら生成されたコードも触らなくて良いはずです。
テスト
この oas モデルはテスト等でも大活躍します。
以下のような感じでモデルからリクエスト、レスポンスを生成するようにしておけば openapi 定義が変更されると静的チェックでエラーになるので修正が超簡単です。
/**
* 正しいレスポンスが返ってくる事
*
* @return void
*/
public function test_正しいレスポンスが返ってくる事()
{
// ダミデータを生成
$job_category = JobCategory::factory()->create();
// 返却されるはずのレスポンスディを生成
$response_model = new OpenAPI\Model\JobCategory();
$response_model->setName($job_category->name);
$response_model->setContent($job_category->content);
$response_model->setImage($job_category->image);
$response_model->setImageUrl(Storage::url($job_category->image));
$response_model->setSortNo($job_category->sort_no);
$response_body = OpenAPIUtility::convertDict($response_model);
/**
* 確認項目
*/
$response = $this->get(sprintf('/api/v1/job_categories/%s', $job_category->id));
// レスポンスに想定する内容が含まれている事。
$response->assertJson($response_body);
// 正しいステータスコードが返ってくる事
$response->assertStatus(200);
}
突っ込まれそうな事
■oas を変えるたびに laravel 側のルートとリクエストクラスを合わせるのがめんどくさい。
→ 頑張って合わせてください。
■oas のヘッダーとかトークン認証とか保障されてなくね?
→ されてません。Openapi で表現できる以上の事を laravel のルートで制御できるのでまあ良いかなあと。あくまで laravel が主。
Discussion