【Laravel】Swagger-PHPを使ってSwagger（OpenAPI）仕様を作成する

# はじめに

こんにちは、プログラミングを始めて約3年のエンジニアのkeitaMaxです。

今回はSwagger-PHPを使ってみたいと思います。

https://zircote.github.io/swagger-php/guide/

# Swagger-PHPとは

>swagger-phpは、PHP ソース コード ファイルから API メタデータを抽出するライブラリです。
>
>アイデアは、アプリケーション内の関連する PHP コードの隣にswagger-php 注釈または属性を追加することです。これらには API に関する詳細が含まれており、swagger-phpそれらは機械可読なOpenAPI ドキュメントに変換されます。
(引用：https://zircote.github.io/swagger-php/guide/)

PHPでAPIを作成したら、自動的にSwagger（OpenAPI）仕様を作成してくれるライブラリのことらしいです。


# インストール

https://zircote.github.io/swagger-php/guide/installation.html

以下コマンドでインストールします。

```
composer require zircote/swagger-php
```


# APIの作成

まずAPIを作成してみます。

`routes/web.php`に以下を追加します。

```php:web.php
Route::resource('/posts', PostController::class);
```

そして、以下のコマンドでControllerを追加します。
```
php artisan make:controller PostController
```

`PostController`を以下のように修正します。

```php:PostController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Lib\UsePost;
use OpenApi\Attributes as OA;

class PostController extends Controller
{
    public function index(): string
    {
        return "index";
    }
}
```

これで/postsをGETで叩いたら、`index`と返ってくる簡単なAPIができました。

今、Dockerで8080ポートで起動しているので、Talend　API Testerで`http://localhost:8081/posts`をたたいてみます。

![スクリーンショット 2024-03-23 19.47.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3715123/e7d40ec0-d956-cc88-b46d-58749a7d4c04.png)

このように`index`と返ってきていることがわかります。

# ドキュメントを自動作成する

以下コマンドでドキュメントを作成します。

```
./vendor/bin/openapi app -o openapi.yaml
```

コマンドが成功すると、`openapi.yaml`ファイルが吐き出されます。

```yaml:openapi.yaml
openapi: 3.0.0
paths:
  /posts:
    get:
      operationId: 5f292ed47bdbfa79356750be0807450c
      responses:
        '200':
          description: AOK
        '401':
          description: 'Not allowed'
```

これでSwagger-PHPを使ってSwagger（OpenAPI）仕様を作成できました。

# もう少し作ってみる

もうしこしAPIを作ってみました。

`PostController`を以下のようにしました。

```php:PostController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Lib\UsePost;
use OpenApi\Attributes as OA;

class PostController extends Controller
{
    #[OA\Get(
        path: '/posts',
        responses: [
            new OA\Response(response: 200, description: 'OK'),
            new OA\Response(response: 401, description: 'Not allowed'),
        ]
    )]
    public function index(): string
    {
        return "index";
    }

    #[OA\Get(
        path: '/posts/create',
        responses: [
            new OA\Response(response: 200, description: 'OK'),
            new OA\Response(response: 401, description: 'Not allowed'),
        ]
    )]
    public function create(): string
    {
        return "create";
    }

    #[OA\Post(
        path: '/posts',
        responses: [
            new OA\Response(response: 200, description: 'OK'),
            new OA\Response(response: 401, description: 'Not allowed'),
        ]
    )]
    public function store(Request $request): string
    {
        return "store" . $request;
    }

    #[OA\Get(
        path: '/posts/{id}',
        responses: [
            new OA\Response(response: 200, description: 'OK'),
            new OA\Response(response: 401, description: 'Not allowed'),
        ]
    )]
    public function show(int $id): string
    {
        return "show" . $id;
    }

    #[OA\Get(
        path: '/posts/{id}/edit',
        responses: [
            new OA\Response(response: 200, description: 'OK'),
            new OA\Response(response: 401, description: 'Not allowed'),
        ]
    )]
    public function edit(int $id): string
    {
        return "edit" . $id;
    }

    #[OA\Put(
        path: '/posts/{id}',
        responses: [
            new OA\Response(response: 200, description: 'OK'),
            new OA\Response(response: 401, description: 'Not allowed'),
        ]
    )]
    public function update(Request $request, int $id): string
    {
        return "update" . $id . $request;
    }

    #[OA\Delete(
        path: '/posts/{id}',
        responses: [
            new OA\Response(response: 200, description: 'OK'),
            new OA\Response(response: 401, description: 'Not allowed'),
        ]
    )]
    public function destroy(int $id): string
    {
        return "destroy" . $id;
    }
}
```

これで以下のコマンドを打ってみます。
```
./vendor/bin/openapi app -o openapi.yaml
```

コマンドが成功すると、`openapi.yaml`ファイルが吐き出されます。

```yaml:openapi.yaml
openapi: 3.0.0
paths:
  /posts:
    get:
      operationId: 5f292ed47bdbfa79356750be0807450c
      responses:
        '200':
          description: OK
        '401':
          description: 'Not allowed'
    post:
      operationId: c2e0b0e26215e6d34ece33ebae25f7df
      responses:
        '200':
          description: OK
        '401':
          description: 'Not allowed'
  /posts/create:
    get:
      operationId: 87880105c55ee709353642ef648ce4ec
      responses:
        '200':
          description: OK
        '401':
          description: 'Not allowed'
  '/posts/{id}':
    get:
      operationId: c8bd68bc05422c879bc2dd429ae5782b
      responses:
        '200':
          description: OK
        '401':
          description: 'Not allowed'
    put:
      operationId: 06c81121178208730e11d2f6b655e78d
      responses:
        '200':
          description: OK
        '401':
          description: 'Not allowed'
    delete:
      operationId: 54788a50da690c5ee1dfdc25425a1f87
      responses:
        '200':
          description: OK
        '401':
          description: 'Not allowed'
  '/posts/{id}/edit':
    get:
      operationId: 21af8f810c09713b3cf9987a1624e4fd
      responses:
        '200':
          description: OK
        '401':
          description: 'Not allowed'
```

こんな感じで色々なAPIの使用が記載されているyamlファイルができました。

# おわりに

簡単に導入することができました。
今後色々設定してみて、フロント側にAPI仕様書的なものを渡せるようにPackage化してみたいと思います。

この記事での質問や、間違っている、もっといい方法があるといったご意見などありましたらご指摘していただけると幸いです。

最後まで読んでいただきありがとうございました！

# 参考文献
https://zircote.github.io/swagger-php/