swagger-phpのアノテーションからAPIドキュメントを自動生成する
概要
PHPでAPIを開発していると、「実装は進むのにドキュメントが追いつかない」「手で書いたAPI仕様がすぐ古くなる」という問題に必ず突き当たります。
この記事では zircote/swagger-php を使い、コード中のアノテーション(PHP 8 の Attribute)から OpenAPI 定義(openapi.json / openapi.yaml)を自動生成し、Swagger UI で閲覧できるようにするまでをハンズオン形式でまとめます。「実装=仕様の唯一の正」にすることで、ドキュメントの陳腐化を構造的に防ぐのがゴールです。
環境
- PHP 8.2 以上
- Composer
zircote/swagger-php6.x 系(本記事執筆時点の最新は 6.10)- Swagger UI もしくは Redoc(表示確認用)
swagger-php 6.x は PHP 8 の Attribute を第一級の記法として扱います。レガシーな docblock(@OA\...)記法は 6.x 系では既定で無効化されており、使い続けるには doctrine/annotations を別途手動インストールする必要があります(この経路自体も非推奨)。本記事では新規採用を想定し、Attribute 記法を中心に扱うため、追加ライブラリは不要です。
発生していた問題
手書きのAPI仕様には次の問題があります。
- 実装を変えてもドキュメント更新が漏れ、仕様と実装が乖離する
- レビュー時に「どちらが正しいのか」が分からなくなる
- 古い仕様を連携先に渡し、手戻りが発生する
根本原因は ドキュメントがコードから分離している ことです。
方針
対策はシンプルで、ドキュメントの一次情報をコードに寄せることです。エンドポイントを実装しているコントローラの直近にアノテーションを書き、コマンド一発で OpenAPI 定義を再生成すれば、実装とドキュメントがずれる余地が減ります。
実装手順
1. インストール
composer require --dev zircote/swagger-php ./vendor/bin/openapi --version
2. Info を1つ用意する
swagger-php は最低でも1つ OA\Info を必要とします。
<?php
namespace App\Http\Controllers;
use OpenApi\Attributes as OA;
#[OA\Info(
version: "1.0.0",
title: "Sample API",
description: "swagger-php で自動生成するサンプルAPIの仕様",
)]
#[OA\Server(url: "https://api.example.com", description: "本番環境")]
class OpenApiSpec
{
// アノテーションを載せるための器
}
3. エンドポイントにアノテーションを付ける
<?php
namespace App\Http\Controllers;
use OpenApi\Attributes as OA;
class UserController extends OpenApiSpec
{
#[OA\Get(
path: "/api/users/{id}",
summary: "ユーザーを1件取得する",
tags: ["Users"],
parameters: [
new OA\Parameter(
name: "id",
in: "path",
required: true,
description: "ユーザーID",
schema: new OA\Schema(type: "integer", example: 1),
),
],
responses: [
new OA\Response(
response: 200,
description: "取得成功",
content: new OA\JsonContent(ref: "#/components/schemas/User"),
),
new OA\Response(response: 404, description: "ユーザーが存在しない"),
],
)]
public function show(int $id)
{
// 実装
}
}
4. スキーマを定義して使い回す
<?php
namespace App\Schemas;
use OpenApi\Attributes as OA;
#[OA\Schema(
schema: "User",
required: ["id", "name"],
properties: [
new OA\Property(property: "id", type: "integer", example: 1),
new OA\Property(property: "name", type: "string", example: "山田太郎"),
new OA\Property(property: "email", type: "string", format: "email", nullable: true),
new OA\Property(property: "created_at", type: "string", format: "date-time"),
],
)]
class UserSchema
{
}
以降は ref: "#/components/schemas/User" で参照して使い回します。
5. POST とリクエストボディ
#[OA\Post(
path: "/api/users",
summary: "ユーザーを新規作成する",
tags: ["Users"],
requestBody: new OA\RequestBody(
required: true,
content: new OA\JsonContent(
required: ["name", "email"],
properties: [
new OA\Property(property: "name", type: "string", example: "山田太郎"),
new OA\Property(property: "email", type: "string", format: "email"),
],
),
),
responses: [
new OA\Response(response: 201, description: "作成成功"),
new OA\Response(response: 422, description: "バリデーションエラー"),
],
)]
public function store()
{
// 実装
}
6. 認証(Bearer トークン)を宣言する
#[OA\SecurityScheme(
securityScheme: "bearerAuth",
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
)]
エンドポイント側で有効化します。
#[OA\Get(
path: "/api/me",
summary: "自分の情報を取得する",
security: [["bearerAuth" => []]],
responses: [ new OA\Response(response: 200, description: "OK") ],
)]
public function me()
{
// 実装
}
7. 定義ファイルを生成する
# YAML で出力 ./vendor/bin/openapi app --output public/openapi.yaml # JSON で出力 ./vendor/bin/openapi app --output public/openapi.json --format json
プログラムから生成する場合は Generator を直接呼びます。
<?php require __DIR__ . '/vendor/autoload.php'; $openapi = \OpenApi\Generator::scan([__DIR__ . '/app']); file_put_contents(__DIR__ . '/public/openapi.yaml', $openapi->toYaml());
8. Swagger UI で表示する
docker run -p 8080:8080 \ -e SWAGGER_JSON=/spec/openapi.yaml \ -v $(pwd)/public:/spec \ swaggerapi/swagger-ui
http://localhost:8080 を開けば、生成された仕様がインタラクティブに表示されます。
確認方法
- 生成コマンドの終了コードが
0であること - 出力された YAML/JSON が OpenAPI 3.x として妥当であること(Swagger Editor に貼るのが手軽)
- Swagger UI 上で各エンドポイント・スキーマ・認証が意図通り表示されること
CIに組み込むなら、生成とバリデーションを1ステップにまとめておくと安全です。
./vendor/bin/openapi app --output /tmp/openapi.json --format json # 生成物を lint / diff で検証する
注意点
- 走査対象ディレクトリを絞る。
vendorを含めると重く、無関係なアノテーションを拾うのでappなど必要なパスだけ指定する。 Infoは必ず1つ。無いとエラーになる。refの綴りミスは気づきにくい。参照名を統一し、スキーマ名を機械的に管理すると事故が減る。- Attribute と docblock を混在させない。6.x 系では docblock(
@OA\...)記法が既定で無効化されており、doctrine/annotationsを入れずに残すと黙って無視される。新規なら Attribute に統一するのが素直。 - 生成物の運用方針を決める。CIで毎回生成して配布するほうが陳腐化しにくい。
まとめ
swagger-php を使えば、実装コードのアノテーションを一次情報として OpenAPI 定義を機械生成できます。要点は3つです。
Infoを1つ用意し、エンドポイントに Attribute を付ける- スキーマは
OA\Schemaとして定義してrefで使い回す vendor/bin/openapiで生成し、CIで再生成・検証する
「ドキュメントをコードに寄せる」だけで、仕様と実装の乖離という慢性的な問題を構造的に解消できます。まずは1エンドポイントから始めてみてください。
