laravel-openapiライブラリ 一括してexampleを定義する方法

# 概要

- laravel-openapiにて一括してexampleを定義する方法をまとめる。

# 前提

- exampleの定義は各スキーマ毎に定義していた。
    - [laravel-openapiライブラリ ドキュメントを出力してみる vol2 リクエストボディー](https://qiita.com/miriwo/items/e02cf0833856117144ba)

        ![laravel-openapiライブラリ_ドキュメントを出力してみる_vol2_リクエストボディー_-_Qiita.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/ca48ace9-2e61-2dcd-9938-9adc2dac777e.png)

- 今回はexampleを個別にではなく一括で定義してみようと思う。

# 準備

1. 下記のように各ファイルを定義した。
    - ルーティング

        ```アプリ名ディレクトリ/routes/api.php
        <?php
        
        use Illuminate\Http\Request;
        use Illuminate\Support\Facades\Route;
        use App\Http\Controllers\UserController;
        
        /*
        |--------------------------------------------------------------------------
        | API Routes
        |--------------------------------------------------------------------------
        |
        | Here is where you can register API routes for your application. These
        | routes are loaded by the RouteServiceProvider within a group which
        | is assigned the "api" middleware group. Enjoy building your API!
        |
        */
        
        Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
            return $request->user();
        });
        
        Route::post('/update', [UserController::class, 'update']);
        ```

    - コントローラー

        ```アプリ名ディレクトリ/app/Http/Controllers/UserController.php
        <?php
        
        namespace App\Http\Controllers;
        
        use App\OpenApi\RequestBodies\UserUpdateRequestBody;
        use Vyuldashev\LaravelOpenApi\Attributes as OpenApi;
        
        #[OpenApi\PathItem]
        class UserController extends Controller
        {
            /**
             * ユーザー情報更新
             *
             * @return void
             */
            #[OpenApi\Operation]
            #[OpenApi\RequestBody(UserUpdateRequestBody::class)]
            public function update()
            {
            
            }
        }
        ```

    - laravel-requestリクエストボディー
    
        ```アプリ名ディレクトリ/app/OpenApi/RequestBodies/UserUpdateRequestBody.php
        <?php
        
        namespace App\OpenApi\RequestBodies;
        
        use App\OpenApi\Schemas\UserUpdateRequestBodySchema;
        use GoldSpecDigital\ObjectOrientedOAS\Objects\MediaType;
        use GoldSpecDigital\ObjectOrientedOAS\Objects\RequestBody;
        use Vyuldashev\LaravelOpenApi\Factories\RequestBodyFactory;
        
        class UserUpdateRequestBody extends RequestBodyFactory
        {
            public function build(): RequestBody
            {
                return RequestBody::create()
                    ->content(
                        MediaType::json()
                            ->schema(UserUpdateRequestBodySchema::ref())
                    );
            }
        }
        ```

    - laravel-requestリクエストボディースキーマ

        ```アプリ名ディレクトリ/app/OpenApi/Schemas/UserUpdateRequestSchema.php
        <?php
        
        namespace App\OpenApi\Schemas;
        
        use GoldSpecDigital\ObjectOrientedOAS\Contracts\SchemaContract;
        use GoldSpecDigital\ObjectOrientedOAS\Objects\AllOf;
        use GoldSpecDigital\ObjectOrientedOAS\Objects\AnyOf;
        use GoldSpecDigital\ObjectOrientedOAS\Objects\Not;
        use GoldSpecDigital\ObjectOrientedOAS\Objects\OneOf;
        use GoldSpecDigital\ObjectOrientedOAS\Objects\Schema;
        use Vyuldashev\LaravelOpenApi\Contracts\Reusable;
        use Vyuldashev\LaravelOpenApi\Factories\SchemaFactory;
        
        class UserUpdateRequestBodySchema extends SchemaFactory implements Reusable
        {
            /**
             * @return AllOf|OneOf|AnyOf|Not|Schema
             */
            public function build(): SchemaContract
            {
                return Schema::object('UserUpdateRequestBody')
                    ->properties(
                        Schema::integer('id')
                            ->description('ユーザーID')
                            ->example(1),
                        Schema::string('name')
                            ->description('ユーザー名')
                            ->example('XXX YYY'),
                        Schema::string('email')
                            ->description('ユーザーメールアドレス')
                            ->example('test@example.com'),
                        Schema::array('foo_infos')
                            ->items(
                                Schema::object()
                                    ->properties(
                                        Schema::integer('id')
                                            ->description('ID')
                                            ->example(1),
                                        Schema::string('info')
                                            ->description('情報')
                                            ->example('hoge hoge'),
                                    )
                            )
                    );
            }
        }
        ```

1. 上記の状態でOpenAPIを出力したところ下記の内容が出力された。

    ```json
    {
        "openapi": "3.0.2",
        "info": {
            "title": "Laravel",
            "version": "1.0.0"
        },
        "servers": [
            {
                "url": "http:\/\/localhost"
            }
        ],
        "paths": {
            "\/api\/update": {
                "post": {
                    "summary": "ユーザー情報更新",
                    "requestBody": {
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/UserUpdateRequestBody"
                                }
                            }
                        }
                    }
                }
            }
        },
        "components": {
            "schemas": {
                "UserUpdateRequestBody": {
                    "type": "object",
                    "properties": {
                        "id": {
                            "description": "ユーザーID",
                            "type": "integer",
                            "example": 1
                        },
                        "name": {
                            "description": "ユーザー名",
                            "type": "string",
                            "example": "XXX YYY"
                        },
                        "email": {
                            "description": "ユーザーメールアドレス",
                            "type": "string",
                            "example": "test@example.com"
                        },
                        "foo_infos": {
                            "type": "array",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "description": "ID",
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "info": {
                                        "description": "情報",
                                        "type": "string",
                                        "example": "hoge hoge"
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    ```

# 方法

1. 下記のようなJSONのexampleを一括して定義したい。

    ```json
    {
      "id": 10,
      "name": "AAA BBB",
      "email": "testtest@example.com",
      "foo_infos": [
        {
          "id": 100,
          "info": "fuga fuga"
        },
        {
          "id": 200,
          "info": "piyo piyo"
        }
      ]
    }
    ```

1. スキーマクラスを下記のように記載する。

    ```アプリ名ディレクトリ/app/OpenApi/Schemas/UserUpdateRequestSchema.php
    <?php
    
    namespace App\OpenApi\Schemas;
    
    use GoldSpecDigital\ObjectOrientedOAS\Contracts\SchemaContract;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\AllOf;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\AnyOf;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\Not;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\OneOf;
    use GoldSpecDigital\ObjectOrientedOAS\Objects\Schema;
    use Vyuldashev\LaravelOpenApi\Contracts\Reusable;
    use Vyuldashev\LaravelOpenApi\Factories\SchemaFactory;
    
    class UserUpdateRequestBodySchema extends SchemaFactory implements Reusable
    {
        /**
         * @return AllOf|OneOf|AnyOf|Not|Schema
         */
        public function build(): SchemaContract
        {
            return Schema::object('UserUpdateRequestBody')
                ->properties(
                    Schema::integer('id')
                        ->description('ユーザーID')
                        ->example(1),
                    Schema::string('name')
                        ->description('ユーザー名')
                        ->example('XXX YYY'),
                    Schema::string('email')
                        ->description('ユーザーメールアドレス')
                        ->example('test@example.com'),
                    Schema::array('foo_infos')
                        ->items(
                            Schema::object()
                                ->properties(
                                    Schema::integer('id')
                                        ->description('ID')
                                        ->example(1),
                                    Schema::string('info')
                                        ->description('情報')
                                        ->example('hoge hoge'),
                                )
                        )
                )
                ->example([
                    'id' => 10,
                    'name' => 'AAA BBB',
                    'email' => 'testtest@example.com',
                    'foo_infos' => [
                        [
                            'id' => 100,
                            'info' => 'fuga fuga',
                        ],
                        [
                            'id' => 200,
                            'info' => 'piyo piyo',
                        ],
                    ],
                ]);
        }
    }
    ```

- 下記のようなJSONが出力された。

    ```json
    {
        "openapi": "3.0.2",
        "info": {
            "title": "Laravel",
            "version": "1.0.0"
        },
        "servers": [
            {
                "url": "http:\/\/localhost"
            }
        ],
        "paths": {
            "\/api\/update": {
                "post": {
                    "summary": "ユーザー情報更新",
                    "requestBody": {
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/UserUpdateRequestBody"
                                }
                            }
                        }
                    }
                }
            }
        },
        "components": {
            "schemas": {
                "UserUpdateRequestBody": {
                    "type": "object",
                    "properties": {
                        "id": {
                            "description": "ユーザーID",
                            "type": "integer",
                            "example": 1
                        },
                        "name": {
                            "description": "ユーザー名",
                            "type": "string",
                            "example": "XXX YYY"
                        },
                        "email": {
                            "description": "ユーザーメールアドレス",
                            "type": "string",
                            "example": "test@example.com"
                        },
                        "foo_infos": {
                            "type": "array",
                            "items": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "description": "ID",
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "info": {
                                        "description": "情報",
                                        "type": "string",
                                        "example": "hoge hoge"
                                    }
                                }
                            }
                        }
                    },
                    "example": {
                        "id": 10,
                        "name": "AAA BBB",
                        "email": "testtest@example.com",
                        "foo_infos": [
                            {
                                "id": 100,
                                "info": "fuga fuga"
                            },
                            {
                                "id": 200,
                                "info": "piyo piyo"
                            }
                        ]
                    }
                }
            }
        }
    }
    ```

- Swagger Viewerで確認すると下記のようにExample Valueには一括で設定したexampleが表示されている。

    ![Swagger_Editor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/88787c57-be9f-35b0-78e5-f3ac065394f4.png)

- Schemaの方は個々で割当たexampleが表示されている。

    ![Swagger_Editor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/230bbc01-d4e4-9d6a-8209-1562101dfa6e.png)