Laravel でカスタムバリデーションとそのテスト

本記事は [Laravel Advent Calendar 2016](http://qiita.com/advent-calendar/2016/laravel) の 6 日目の記事です。

## はじめに
公式のカスタムバリデーションのドキュメントが今ひとつ分かりにくいので、手順をまとめることにした。
今回はサンプルとして ふりがな のバリデーションを作成する。

※以前に書いた通常バリデーションの応用
http://qiita.com/komatzz/items/422bcba9847ca3a79fe9

##環境
- CentOS 7
- PHP 7
- Laravel 5.3


## カスタムバリデーション作成手順

### サービスプロバイダー作成

> php artisan make:provider ValidatorServiceProvider

作成されたクラスを以下のように編集。

```app/Providers/ValidatorServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Domain\CustomValidator;

class ValidatorServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
      \Validator::resolver(function ($translator, $data, $rules, $messages) {
          return new CustomValidator($translator, $data, $rules, $messages);
      });
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}
```

### 作成したサービスプロバイダーを設定ファイルに追加

```config/app.php
'providers' => [
    ...

    App\Providers\ValidatorServiceProvider::class,

    ...
```

### カスタムバリデーションのロジックを書くファイルを作成する
今回は`domain`というディレクトリを作成し、その下に`CustomValidator.php`を作成した。

```domain/CustomValidator.php
namespace Domain;

class CustomValidator extends \Illuminate\Validation\Validator
{
  /**
  * ふりがなのバリデーション
  *
  * @param $attribute
  * @param $value
  * @param $parameters
  * @return bool
  */
  public function validateKana($attribute, $value, $parameters)
  {
      if (mb_strlen($value) > 100) {
          return false;
      }  

      if (preg_match('/[^ぁ-んー]/u', $value) !== 0) {
          return false;
      }

      return true;
  }
}
```

### オートロードの設定

`composer.json` の psr-4 設定に に`domain`ディレクトリ配下をオートロードできるように追加する
※これに気づかなくてハマった

```composer.json
...

"autoload": {
  "psr-4": {
      "App\\": "app/",
      "Domain\\": "domain/",
  }
}

...
```

### エラーメッセージファイルに追加

```resources/lang/ja/validation.php
return [
   ...

   'kana' => ':attributeはひらがなで入力をしてください。',
   
   ...

   'attributes' => [
       ...

        'family_name_kana' => 'ふりがな（せい）',
        'given_name_kana'  => 'ふりがな（めい）',

       ...
   ],
```

### カスタムバリデーションを使う

使い方は通常のバリデーションと同じで、
`'family_name_kana' => 'required|kana',`
のように使いたいバリデーションを`|`区切りで指定すれば良い。

```app/Http/Controllers/RegistersController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Validator;

class RegistersController extends Controller
{
    ...
    
    public function confirm(Request $request, $registerToken)
    {
        \Validator::make(
            [
                'family_name_kana' => trim($request->get('family_name_kana')),
                'given_name_kana'  => trim($request->get('given_name_kana')),
            ],
            [
                'family_name_kana' => 'required|kana',
                'given_name_kana'  => 'required|kana',
            ]
        }->validate();
    }
    return view('registers/registers_confirm');
    
    ...
}
```


### バリデーション実施結果
![valid_kana.png](https://qiita-image-store.s3.amazonaws.com/0/57002/a43ea569-4950-2f6b-f573-83159e96b673.png)

`ふりがな（せい）`をカタカナで POST したので、ちゃんと引っかかっている。

## テストコード作成

ちゃんとバリデーションできているかどうかテストするコードを書く。

```tests/domain/validation/KanaTest.php
<?php

/**
 * かなのバリデーションのテストクラス
 */

namespace Tests\Domain\Validation;

use Validator;

class KanaTest extends \TestCase
{
    /**
     * 正常系テスト
     *
     * @dataProvider successParamsProvider
     * @param array $values
     */
    public function testSuccess($values)
    {
        $validator = \Validator::make(
            [
                'family_name_kana' => $values,
                'given_name_kana'  => $values,
            ],
            [
                'family_name_kana' => 'required|kana',
                'given_name_kana'  => 'required|kana',
            ]
        );

        $this->assertTrue(
            $validator->passes()
        );
    }

    /**
     * 正常系パラメータ用のデータプロバイダー
     *
     * @return array
     */
    public function successParamsProvider()
    {
        return [
            'ひらがな' => [
                'やきにくていしょく',
            ],
            '大きな文字列（かなは100文字まで）' => [
                str_repeat('あ', 100)
            ],
        ];
    }

    /**
     * 異常系テスト
     *
     * @dataProvider failParamsProvider
     * かなに不適切な文字が入力されている
     *
     * @param array $values
     */
    public function testFail($values)
    {
        $validator = \Validator::make(
            [
                'family_name_kana' => $values,
                'given_name_kana'  => $values,
            ],
            [
                'family_name_kana' => 'required|kana',
                'given_name_kana'  => 'required|kana',
            ]
        );

        $this->assertTrue(
            $validator->fails()
        );
    }

    /**
     * 異常系パラメータ用のデータプロバイダー
     *
     * @return array
     */
    public function failParamsProvider()
    {
        return [
            '数字' => [
                1,
            ],
            '漢字' => [
                '焼肉定食',
            ],
            'カタカナ' => [
                'ヤキニクテイショク',
            ],
            '大きな文字列（かなは100文字まで）' => [
                str_repeat('あ', 101),
            ],
            'NULL' => [
                null,
            ],
            '空' => [
                '',
            ],
            'スクリプト' => [
                '<script>alert(1);</script>',
            ],
            '\'' => [
                '\'',
            ],
            '/' => [
                '/',
            ],
            '<' => [
                '<',
            ],
            '¥' => [
                '¥',
            ],
        ];
    }
}
```

### 解説
- ひらがな かつ、100文字以内かどうかをテストしている
- PHPUnit の[データプロバイダー](https://phpunit.de/manual/current/ja/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers)という機能を使うと簡潔にテストコードが書けて、失敗したときにどこで失敗したのか分かりやすい

![image](https://qiita-image-store.s3.amazonaws.com/0/57002/efbb1893-de00-1d59-ff8f-b4415543ef39.png)

## ひとこと
これでバリデーション処理に関しては、ほとんど対応できるようになった。