Laravel + SwaggerUI + DockerでAPI仕様書を表示する

# はじめに
DockerでSwaggerUIコンテナを立ち上げ、Laravelで作成したAPIからAPI仕様書を表示します。

## やりたいこと
- SwaggerUIでAPI仕様書を表示したい

## この記事で触れないこと
- LaravelのAPI開発手順など

## ディレクトリ構成
```
.
├── docker-file
│   └── web
│       ├── Dockerfile
│       └── php.ini
├── swagger-docs
│       └── swagger.json
├── volumes
│   └── swagger-api
└── docker-compose.yml
```
# 各ファイルの内容
## docker-compose.yml
今回はswagger-uiに焦点を当てている為、dbの項目の記載は省略しています。

```docker-compose.yml
version: '3'

services:

  web:
    container_name: web
    build: ./docker-file/web/
    ports:
      - 8080:80
    volumes:
      - ./volumes/swagger-api:/var/www/html/swagger-api
      
  db:
    container_name: mysql
    ....

  swagger:
    container_name: swagger-ui
    image: swaggerapi/swagger-ui
    volumes:
      - ./swagger-docs/swagger.json:/usr/share/nginx/html/swagger.json
    environment:
      API_URL: swagger.json
    ports:
      - "8081:8080"
```

## docker-file/web/Dockerfile

```Dockerfile
FROM php:7.4-apache

COPY ./php.ini /usr/local/etc/php/

RUN apt-get update \
  && apt-get install --no-install-recommends -y git curl wget sudo libfreetype6-dev libjpeg62-turbo-dev libmcrypt-dev libmcrypt-dev libxml2-dev libpq-dev libzip-dev libpq5 postgresql-client default-mysql-client libicu-dev libonig-dev \
  && mv /etc/apache2/mods-available/rewrite.load /etc/apache2/mods-enabled \
  && docker-php-ext-configure gd --with-freetype=/usr/include/ --with-jpeg=/usr/include/ \
  && docker-php-ext-install -j$(nproc) zip gd xml pdo pdo_mysql mysqli soap intl \
  && rm -r /var/lib/apt/lists/*

RUN /bin/sh -c a2enmod rewrite
```

## docker-file/web/php.ini

```php.ini
[Core]
display_errors = On
error_reporting = E_ALL
error_log = /var/log/apache2/error.log
log_errors = On

[Date]
date.timezone = 'Asia/Tokyo'

[mbstring]
mbstring.language = Japanese
mbstring.internal_encoding = auto
mbstring.http_input = auto
mbstring.http_output = auto
mbsting.encoding_translation = Off
mbstring.detect_order = auto

```

## swagger-docs/swagger.json
空ファイルを用意しておきます。

## 実行
docker-composeで起動します。

```
$ docker-compose up -d
Creating network "swagger-ui-docker_default" with the default driver
Creating swagger-ui ... done
Creating mysql      ... done
Creating web        ... done
```

#Swagger

## インストール
composerで必要なライブラリをインストールします。
最新は3.x系ですが、以前使っていた2.x系とかなり変わっているようなので今回は2.x系の最新版をインストールします。

```shell
$ composer require zircote/swagger-php:2.0.16
```

## Swagger用phpファイル設置
ドキュメントを生成したいディレクトリにphpファイルを作成します。
今回はControllersディレクトリの直下に配置します。

```php:/app/Http/Controllers/swagger.php
<?php

/**
 * @SWG\Swagger(
 *     @SWG\Info(
 *         version="1.0",
 *         title="Laravel API Specification",
 *     ),
 * )
 */
```

## ControllerにSwagger用コメントを記載
今回は例としてGet/Post/Put/Deleteを持つProductControllerを作成しました。
メソッドの中身は省略しています。

```php:/app/Http/Controllers/Api/ProductController.php
<?php

....

class ProductController extends Controller
{

    /**
     * @SWG\Get(
     *     path="/api/product/{product_id}",
     *     summary="商品情報取得",
     *     description="商品情報を取得します。",
     *     produces={"application/json"},
     *     tags={"Product"},
     *     @SWG\Parameter(
     *         name="product_id",
     *         description="商品ID",
     *         in="path",
     *         required=true,
     *         type="integer"
     *     ),
     *     @SWG\Response(
     *         response=200,
     *         description="Success",
     *     ),
     *     @SWG\Response(
     *         response=400,
     *         description="Bad Request error",
     *     ),
     *     @SWG\Response(
     *         response=401,
     *         description="Unauthorized error"
     *     ),
     * )
     */
    public function show($product_id)
    {
        ....
    }

    /**
     * @SWG\Post(
     *     path="/api/product",
     *     summary="商品情報登録",
     *     description="商品情報を登録します。",
     *     produces={"application/json"},
     *     tags={"Product"},
     *     @SWG\Parameter(
     *         in="body",
     *         name="Product",
     *         description="List of product object",
     *         @SWG\Schema(
     *             type="object",
     *             @SWG\Property(property="code", type="string", description="商品コード"),
     *             @SWG\Property(property="name", type="string", description="商品名"),
     *             @SWG\Property(property="price", type="integer", description="販売価格"),
     *         )
     *     ),
     *     @SWG\Response(
     *         response=200,
     *         description="Success",
     *     ),
     *     @SWG\Response(
     *         response=400,
     *         description="Bad Request error",
     *     ),
     *     @SWG\Response(
     *         response=401,
     *         description="Unauthorized error"
     *     ),
     * )
     */
    public function store(Request $request)
    {
        ....
    }

    /**
     * @SWG\Put(
     *     path="/api/product",
     *     summary="商品情報更新",
     *     description="商品情報を更新します。",
     *     produces={"application/json"},
     *     tags={"Product"},
     *     @SWG\Parameter(
     *         in="body",
     *         name="Product",
     *         description="List of product object",
     *         @SWG\Schema(
     *             type="object",
     *             @SWG\Property(property="product_id", type="integer", description="商品ID"),
     *             @SWG\Property(property="code", type="string", description="商品コード"),
     *             @SWG\Property(property="name", type="string", description="商品名"),
     *             @SWG\Property(property="price", type="integer", description="販売価格"),
     *         )
     *     ),
     *     @SWG\Response(
     *         response=200,
     *         description="Success",
     *     ),
     *     @SWG\Response(
     *         response=400,
     *         description="Bad Request error",
     *     ),
     *     @SWG\Response(
     *         response=401,
     *         description="Unauthorized error"
     *     ),
     * )
     */
    public function update(Request $request)
    {
        ....
    }

    /**
     * @SWG\Delete(
     *     path="/api/product",
     *     summary="商品情報削除",
     *     description="商品情報を削除します。",
     *     produces={"application/json"},
     *     tags={"Product"},
     *     @SWG\Parameter(
     *         in="body",
     *         name="Product",
     *         description="List of product object",
     *         @SWG\Schema(
     *             type="object",
     *             @SWG\Property(property="product_id", type="integer", description="商品ID"),
     *         ),
     *     ),
     *     @SWG\Response(
     *         response=200,
     *         description="Success",
     *     ),
     *     @SWG\Response(
     *         response=400,
     *         description="Bad Request error",
     *     ),
     *     @SWG\Response(
     *         response=401,
     *         description="Unauthorized error"
     *     ),
     * )
     */
    public function destroy(Request $request)
    {
        ....
    }
}
```

## swagger.json生成
以下コマンドで、記載したコメントからswagger.jsonを生成します。

-oの後ろが出力するディレクトリです。
出力する場所はどこでも大丈夫ですが、最終的にdocker側のswagger-docsディレクトリに配置する為ここで指定しています。

```shell
$ vendor/bin/swagger app/Http/Controllers/ -o ../../swagger-docs/
```

## ブラウザで確認
docker-compose.ymlで先ほど出力したswagger-docs/swagger.jsonを参照するようにしているので、以下URLにアクセスするとAPI仕様書が確認できるようになっています。

http://localhost:8081/

![localhost_8081_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/672074/760267bd-2c29-3ce0-3ad5-b5f6c97bc9f6.png)