swagger-phpのスキーマ定義はannotationよりもAttributeの方が断然楽でした

# はじめに

弊社のプロダクトではOpenApIを活用して、実装コード中にスキーマ定義を記入し、APIドキュメントを生成しています。これまではAnnotation機能を利用していましたが、Attributeを利用した記述の方が書きやすかったため、それについてまとめてみました。

# 先に結論
- Attributeを利用することでIDEのコード補完が効く
- Annotation特有のインデント崩れの心配がない


# 書き方の違いを比較してみる
AnnotationとAttributesでどのように書き方が異なるのか比較できるように両方のパターンを用意しました。

## Annotationで書くパターン
### Controller
[オフィシャルドキュメント](https://zircote.github.io/swagger-php/#minimal-an)より転載
```php
<?php

use OpenApi\Annotations as OA;

/**
 * @OA\Info(
 *     title="My First API",
 *     version="0.1"
 * )
 */
class OpenApi {}

class MyController {

    /**
     * @OA\Get(
     *     path="/api/data.json",
     *     @OA\Response(
     *         response="200",
     *         description="The data"
     *     )
     * )
     */
    public function getResource() {
        // ...
    }
}
```

### Responder
```php
<?php

use OpenApi\Annotations as OA;

/**
 * @OA\Schema(
 *     schema="UserResponse",
 *     title="ユーザのレスポンス",
 *     description="ユーザのレスポンススキーマ",
 *     required={"id", "username", "email", "phone"}
 * )
 */
class UserResponse
{
    public function __construct(
        /**
         * @OA\Property(property="id", description="id", type="integer", nullable=false)
         */
        public int $id,
        /**
         * @OA\Property(property="username", description="ユーザー名", type="string", example="theUser", nullable=false)
         */
        public string $username,
        /**
         * @OA\Property(property="email", description="メールアドレス", type="string", example="john@example.com", nullable=false)
         */
        public string $email,
        /**
         * @OA\Property(property="phone", description="電話番号", type="string", example="0322222222", nullable=true)
         */
        public ?string $phone,
    )
    {
    }
}
```


# Attributeを利用した書き方
## Controller
[オフィシャルドキュメント](https://zircote.github.io/swagger-php/#minimal-at)より転載

```php
<?php

use OpenApi\Attributes as OA;

#[OA\Info(title: "My First API", version: "0.1")]
class OpenApi {}

class MyController {

    #[OA\Get(path: '/api/data.json')]
    #[OA\Response(response: '200', description: 'The data')]
    public function getResource() {
        // ...
    }
}
```

## Responder

```php
<?php
​
use OpenApi\Attributes as OA;
​
#[
  OA\Schema(
    schema: 'UserResponse',
    title: 'ユーザのレスポンス',
    description: 'ユーザのレスポンススキーマ',
    required: ['id', 'username', 'email', 'phone']
  )
]
class UserResponse
{
    public function __construct(
        #[OA\Property(property: 'id', description: 'id', type: 'integer', nullable: false)]
        public int $id,
        #[OA\Property(property: 'username', description: 'ユーザー名', type: 'string', example: 'theUser', nullable: false)]
        public string $username,
        #[OA\Property(property: 'email', description: 'メールアドレス', type: 'string', example: 'john@example.com', nullable: false]
        public string $email,
        #[OA\Property(property: 'phone', description: '電話番号', type: 'string', example: '0322222222', nullable: true)]
        public ?string $phone,
    )
    {
    }
}
```

## IDEで表示されるコードの様子
Attributesの方はIDEのコード補完が効いて見やすくなります。上記のコードサンプルをPhpStormで表示した時のものになります。

### Annotations
![スクリーンショット 2023-05-14 23.07.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94596/9067bce1-9d78-8b60-e5f6-8851b892d21b.png)



### Attributes
![スクリーンショット 2023-05-14 23.08.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/94596/628899a2-2163-baa9-caf5-4df14dd4f36e.png)



# Attributeの書き方Tips
よく利用する書き方の例を紹介させていただきます。参考にして見てください。
詳細については[こちらの公式ドキュメント](https://zircote.github.io/swagger-php/reference/attributes.html)を確認ください。

## リクエストとレスポンスのスキーマ定義

```php
#[OA\RequestBody(
    request="User",
    description="A user that needs to be added",
    required=true,
    @OA\JsonContent(ref="#/components/schemas/User")
)]
#[OA\Response(
    response="200",
    description="User added successfully",
    @OA\JsonContent(ref="#/components/schemas/User")
)]
public function addPet() { /* ... */ }
```

## パラメータの定義

```php
#[OA\Parameter(
    name="id",
    in="query",
    description="ID of the user that needs to be fetched",
    required=true,
    @OA\Schema(type="integer", format="int64")
)]
public function getUserById() { /* ... */ }
```

## タグと要約

```php
#[OA\Get(
    path="/users",
    tags={"users"},
    summary="Get all users",
)]
public function getUsers() { /* ... */ }
```

少しでもお役に立てれば幸いです🙇‍♂️