Laravelのmax file sizeのValidationメッセージでKBをMBにする方法

## 概要
タイトル通りです。
max file sizeのValidationはデフォルトでKB表示になっているので、それをMBやGBに変更する方法を調べたので備忘録です。

## 環境
PHP: 7.2.22
Laravel: 6.18.3

## 事前準備
以下のようにRequestオブジェクトにvalidationのルールを記述すると「inputFile」フォームからアップロードするファイルの上限を1MBに設定します。

```php:app/Http/Requests/FileUploadRequest.php
    public function rules()
    {
        return [
            'inputFile' => 'max:1024'
        ];
    }
```

1MB以上のファイルをアップロードした場合は以下のように「The input file may not be greater than 1024 kilobytes.」と表示されるので、これをメガバイト表示に修正していきます。
![スクリーンショット 2020-04-12 16.37.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183878/062227c7-b76c-c7cb-f9b8-8d1933e78519.png)

## 設定変更
### 方法1: メッセージを固定する
一番手軽に解決できます。
該当のフォームがvalidationに引っかかった場合に表示するメッセージを〜megabytesで固定します。

```php:app/Http/Requests/FileUploadRequest.php
    public function rules()
    {
        return [
            'inputFile' => 'max:1024'
        ];
    }

    public function messages()
    {
        return [
            'inputFile.max' => 'The input file may not be greater than 1 megabytes.'
        ];
    }
```
![スクリーンショット 2020-04-12 16.58.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183878/bb3a31af-1746-772b-aef3-68aa143e357a.png)

### 方法2: カスタムValiationルールを作成する
方法1のやり方だと上限の1MBが2MB等に変更になったときにメッセージも都度修正しないといけないので面倒です。
なので、KBではなくMB表示できるような新しいvalidationのルールを作成します。
[参考1(公式ドキュメント)](https://laravel.com/docs/6.x/validation#using-extensions)
[参考2(stack overflow)](https://stackoverflow.com/questions/34414278/how-to-change-laravel-validation-message-for-max-file-size-in-mb-instead-of-kb)

```php:app/Providers/AppServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\ServiceProvider;
use InvalidArgumentException;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        // max_mb ファイルサイズの最大値をメガバイトで指定するvalidationルールを追加する
        Validator::extend('max_mb', function ($attribute, $value, $parameters, $validator) {
            // パラメータ数のチェック
            $this->requireParameterCount(1, $parameters, 'max_mb');

            // アップロードに成功しているかの判定
            if ($value instanceof UploadedFile && ! $value->isValid()) {
                return false;
            }

            // ファイルサイズをMBに変換
            $megaBytes = $value->getSize() / 1024 / 1024;

            // $parametersにルールで設定した引数より小さければ問題なし
            return $megaBytes <= $parameters[0];
        });

        // validationメッセージ「The :attribute may not be greater than :max_mb megabytes.」の
        // 「:max_mb」部分をパラメータとして与えられた値と置き換える
        Validator::replacer('max_mb', function ($message, $attribute, $rule, $parameters) {
            return str_replace(':max_mb', $parameters[0], $message);
        });
    }

    /**
     * ルールに与えられるパラメータ数のチェックを行う
     *
     * @param integer $count
     * @param array $parameters
     * @param string $rule
     * @return void
     */
    protected function requireParameterCount($count, $parameters, $rule)
    {
        if (count($parameters) < $count) {
            throw new InvalidArgumentException("Validation rule $rule requires at least $count parameters.");
        }
    }
}
```

```php:validation.php
    'max_mb' => 'The :attribute may not be greater than :max_mb megabytes.'
```

AppServiceProviderにValidationルールを書くのが嫌な場合は、新しくServiceProviderを作成してそっちに記述する必要があります。

### 方法3: ルールオブジェクトを作成する
方法2はLaravel5.4までで行われていた方法のようで、Laravel5.5以降はルールオブジェクトを作成することでのカスタムValidationルールの作成が可能でしたので、そちらも試します。
[参考(公式ドキュメント)](https://laravel.com/docs/6.x/validation#using-rule-objects)

まず、下記コマンドを実行します。

```bash
$ php artisan make:rule MegaBytes
```

`app/Rules/MegaBytes.php`が作成されたので以下のように編集します。

```php:MegaBytes.php
<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class MegaBytes implements Rule
{
    protected $param;

    public function __construct($param)
    {
        $this->param = $param;
    }

    public function passes($attribute, $value)
    {
        $megaBytes = $value->getSize() / 1024 / 1024;

        return $megaBytes <= $this->param;
    }

    public function message()
    {
        return trans('validation.max_mb', ['max_mb' => $this->param]);
    }
}
```
`message`メソッドでは`trans`ヘルパーを利用して方法2と同様にvalidation.phpにメッセージを記述して、それを呼び出しています。
このルールを呼び出す時はForm Requestだと以下のように書けます。

```php
<?php

namespace App\Http\Requests;

use App\Rules\MegaBytes;
use Illuminate\Foundation\Http\FormRequest;

//
// 中略
// 
    public function rules()
    {
        return [
            'inputFile' => [new MegaBytes(1)]
        ];
    }
```

### 方法4: Validator::replacerだけで値を修正する(2020/05/02追記)
コメントでご指摘いただきました。多謝。
方法2では`Validator::extend`でvalidationルールを追加していますが、`Validator::replacer`だけでも同じような動作を実現できます。

```php
Validator::replacer('max', function ($message, $attribute, $rule, $parameters) {
    return str_replace(':max_mb', $parameters[0] / 1024, $message);
});
```

```php
    public function rules()
    {
        return [
            'inputFile' => 'max:1024'
        ];
    }

    public function messages()
    {
        return [
            'inputFile.max' => 'The input file may not be greater than :max_mb megabytes.'
        ];
    }
```



## 所感
個人的には方法2が分かりやすかったです。

他にやり方があるようでしたら是非コメントをお願いしますー。