Laravelで更新項目をkey-valueで取得して動的にValidationを実施して更新を実施する

## TL; DR
APIの開発をしていると、通常はFormRequestで更新項目を取得し、validationを実施します。

しかし、更新する項目のみをリクエストとして送信する、かつ１項目のみとする場合は、通常の実装ではこの限りとはなりません。

そのため、少しとトリッキー~~冗長~~なやり方で実装してみます。

## 環境
- PHP7.4
- Laravel7.2


## 通常の更新処理の実装
通常、以下の要領で更新処理のリクエストを送信します。

```json
{
  "name": "名前",
  "mail": "test@exmaple.com",
  "gender": 1 
}
```

このリクエストに対応するFormRequestは、以下のとおりです。

```php
<?php


namespace App\Http\Request\Api;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class UpdateRequest extends FormRequest
{
    /**
     * @return bool
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules(): array
    {
        return [
            'name' => ['required', 'string'],
            'mail' => ['required', 'email'],
            'gender' => ['required', Rule::in(1,2)],
        ];
    }
    
    /**
     * バリデーションエラーのカスタム属性の取得
     *
     * @return array
     */
    public function attributes()
    {
        return [
            'name' => '名前',
            'mail' => 'メールアドレス',
            'gender' => '性別',
        ];
    }
}

```

## key-value形式での実装
今回は、以下の形式で更新する項目名をkeyに、更新する値をvalueに含めたリクエストを送信します。

```json
{
  "key": "mail",
  "value": "test@exmaple.com"
}
```

この場合、通常通りの実装では、Validationは十分にできません。それは、keyで渡される項目をどのルールを適用してValidationの検証をすればよいかわからないからです。

そのため、Validationをする項目を動的に設定するように対応します。


```php
<?php


namespace App\Http\Request\Api;

use App\Enum\Gender;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class UpdateRequest extends FormRequest
{
    private array $keys = [
        'name', 'mail', 'gender'
    ];

    /**
     * @return bool
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * @return array
     */
    public function rules(): array
    {
        return [
            'key' => ['required', 'string', Rule::in($this->keys)],
            'value' => ['required'],
            'name' => ['max:15'],
            'mail' => ['email'],
            'gender' => [Rule::in(Gender::validateList())],
        ];
    }

    /**
     * Get data to be validated from the request.
     *
     * @return array
     */
    public function validationData()
    {
        $data = $this->all();
        if (empty($data['key']) || empty($data['value'])) return $data;

        // keyで設定された値を連想配列のキーとして設定する
        $data[$data['key']] = $data['value'];

        return $data;
    }

    /**
     * バリデーションエラーのカスタム属性の取得
     *
     * @return array
     */
    public function attributes()
    {
        return [
            'key' => '項目名',
            'value' => '属性値',
            'name' => '名前',
            'mail' => 'メールアドレス',
            'gender' => '性別',
        ];
    }
}
```

先ず、`ruleメソッド`では、`key`と`value`を必須項目に設定します。また、このとき設定可能とする`key`の値を`$keys`で取得し、keyのValidationルールに`Rule::in($thit->keys)`を設定します。

しかし、そのままだとValdiationの検証をしたい項目とそのルールが紐づいていません。そのため、Validationが実施される前に呼び出さる`validationDataメソッド`にて、`あたかもその項目でリクエストが来たかのようにふるまう`よう、値の置き換えをします。

```php
     public function validationData()
    {
        $data = $this->all();
        if (empty($data['key']) || empty($data['value'])) return $data;

        // keyで設定された値を連想配列のキーとして設定する
        $data[$data['key']] = $data['value']; // ここで値の置き換え

        return $data;
    }

```

この処理をすることで、あたかも以下の形式のリクエストが送信されたかのようにふるまうことができます。


変換前のリクエスト

```json
{
  "key": "mail",
  "value": "test@exmaple.com"
}
```

変換後のリクエスト

```json
{
  "mail": "test@exmaple.com"
}
```

更新処理の際には、key-valueの各値を更新値の連想配列に含めれば更新が実施されます。

```php
    /**
     * @param UpdateRequest $request
     * @return JsonResponse
     */
    public function __invoke(int $id, UpdateRequest $request): JsonResponse
    {
        $user = User::find(id)->get();

        $user->update([
            $request->get('key') => $request->get('value')
        ]);

        return response()->json($user, 200);
    }
```

果たして、使いどころがあるかはわかりませんが、動的にValidationを行う一例でした。