Laravel4, 5 カスタムバリデートの実装

#Laravelでのカスタムバリデート実装方法
カスタムバリデートの実装方法は、公式に記載があります。
[Laravel Custom Validation Rules(公式)](http://laravel.com/docs/5.0/validation)
[Laravel Custom Validation Rules(日本語訳)](http://readouble.com/laravel/5/0/0/ja/validation.html#custom-validation-rules)
書いてあるように実装するだけで簡単にできますが、
どこにどうやって書いたらどうなるのか、がなかなか読めないと思います。
(ソースコードを読めばありますが・・)
現在Laravel4、Laravel5の両方がありますが、4から5に移行しようとしている方も多いでしょうし、
しばらく4を使い続ける方も多いと思いますので、
なるべく両方で共通する実装方法を紹介します。

## どこに実装すればいいのか？(4,5共通)
Laravel4では**app/start/global.php**というファイルがありますが、
Laravel5では相当するファイルはありません。
共通で実装に利用できるのにふさわしい場所は、サービスプロバイダーです。
初めて利用するのにいきなり壁が現れた感があるかもしれませんが、
こういう風に実装すればいいんだな、とルールのようなものだと思って覚えていくと良いでしょう！
(詳しく解説すると長文になるので調べてみてください。)
4,5共通でValidatorServiceProviderというクラスを作ったとして進めます。

### サービスプロバイダー
サービスプロバイダーは、Illuminate\Support\ServiceProvider を継承しなければなりません。
必ず実装しなければならないメソッドは register メソッドです。
bootはあってもなくても構いませんが、用意だけしておきましょう。
registerが先に実行され、bootはその後に実行されます。
ここも詳しく書くと長くなるので省きますが、
Laravel自体このサービスプロバイダーを利用して各コンポーネントが実装されていて、
それぞれのコンポーネントによって組み込まれるタイミングがあり(eagerとかです)
カスタムバリデーションを実装する場合はbootに記述します。覚えておきましょう！
下記がデフォルトの状態です。

```php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class ValidatorServiceProvider extends ServiceProvider
{

    /**
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * @return void
     */
    public function register()
    {
        //
    }

}
```

### Laravel4の場合
作成コマンドはありませんので、コピペなどでもいいので上記のものを用意しましょう。
composer.jsonでデフォルトのclassmapで利用している方は適当なディレクトリに設置して

```bash
$ composer dump-autoload
```
またはPSR-4で追加、もしくはclassmapもPSR-4変更などに変更すると便利です

```json:composer.json
    "autoload": {
        "classmap": [
            "app/database/migrations",
            "app/database/seeds"
        ],
        "psr-4": {
            "App\\": "app/Application"
        }
    },
```
など

### Laravel5の場合
サービスプロバイダーのファイル自体はartisanコマンドでも作成できます

```bash
$ php artisan make:provider ValidatorServiceProvider
```

5の場合はProviders配下に追加されます。
またこのbootでメソッドインジェクションが利用できます。

### 忘れずにproviders登録
作成したらconfig/app.phpのproviders配列に追記します。

```php
'providers' => [
    // 
    'App\Providers\ValidatorServiceProvider',
]
```

これで実装準備はOKです

## どうやって拡張するのか
サービスプロバイダーのbootに次のように書いてしまいましょう。

```php
    protected function boot()
    {
        $this->app['validator']->resolver(function($translator, array $data, array $rules, array $messages, array $customAttributes) {
            return new App\CustomValidator($translator, $data, $rules, $messages, $customAttributes);
        });
    }
```

カスタムバリデートのクラスはなんでもかいません。
拡張するためにValidatorクラスのresolverメソッドを利用します。
ルール追加では、他にはextendメソッドを利用する方法もありますが、
extendの場合はカスタムルールを一つずつ追加していく必要が有ります。
resolverはクラスで追加することができます。
**$this->app['validator']->resolver** はLaravelのコンテナに直アクセスして
Illuminate\Validation\Factoryインスタンスを取得しています。

```php
\Validator::resolver(function($translator, $data, $rules, $messages, $customAttributes) {
    return new CustomValidator($translator, $data, $rules, $messages, $customAttributes);
});
```
と意味は同じで、どちらで書いても構いません。
拡張には **\Illuminate\Validation\Validator** を継承する必要が有ります。
例えば日本の郵便番号の簡単なバリデーションを実装したとすると下記のような感じになります。

```php
class CustomValidator extends \Illuminate\Validation\Validator
{

    /**
     * @param $attribute
     * @param $value
     * @param $parameters
     * @return int
     */
    public function validateJpZipCode($attribute, $value, $parameters)
    {
        return preg_match("/^[0-9\-]{7,8}+$/i", $value);
    }
}
```
$valueに指定したエレメントの値が入ります。
$parametersはカスタムルールで引数を利用する場合に利用します。

プレフィックスに *validate* をつけなければいけません！
作ったカスタムルールはバリデータを実行したりするところで他のものと同じように次のように指定します。

```php
$rules = [
    'zipcode' => 'required|jpZipCode',
];
```

頭は小文字になりますが、これだけです。
## メッセージの指定方法
langディレクトリを利用して多言語対応などをする場合は、配列のcustomキーに次のように追加します。

```php:lang/言語.php
'custom' => [
    'zipcode' => [
        'jp_zip_code' => ':attributeを正しく入力して下さい',
    ],
```

もしくはValidator::make()で直接指定してしまう方法です。

### Laravel4
```php
$messages = array(
    'zipcode.jp_zip_code' => ':attributeを正しく入力してね',
);
$input = \Input::only(['zipcode', 'name']);
$validator = Validator::make($input, $rules, $messages);

if ($validate->passes()) {
    return true;
}
// エラーメッセージ取得
$this->setErrors($validate->messages());
```

### Laravel5
フォームリクエストを利用すると下記のようになります。

```php
namespace App\Http\Requests;

use Symfony\Component\HttpFoundation\JsonResponse;

class SimpleRequest extends Request
{

    /**
     * @return array
     */
    public function rules()
    {
        return [
            "z" => "required|jpZipCode"
        ];
    }

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

    /**
     * @param array $errors
     * @return JsonResponse
     */
    public function response(array $errors)
    {
        return new JsonResponse($errors, 403);
    }

    public function messages()
    {
        return [
            'zipcode.jp_zip_code' => ':attributeを正しく入力してね'
        ];
    }
}
```

リダイレクト先はルーティング名を指定したりコントローラーで指定したり、
上記のようにリダイレクトではなく、jsonでレスポンスそのものを返却したりできます
若干の実装方法の違いはありますが、基本的には一緒です。

# カスタムバリデートを使いこなす
いろいろありますが、比較的よく使うものを紹介します。

## カスタムバリデートでフォーム内の他の値も利用したい
```php
    public function validateJpZipCode($attribute, $value, $parameters)
    {
        dd($this->getValue('name'));
        return preg_match("/^[0-9\-]{7,8}+$/i", $value);
    }
```

## callbackしたい

```php
    public function validateJpZipCode($attribute, $value, $parameters)
    {
        $this->after(function() {
            echo "hello!";
        });
        return preg_match("/^[0-9\-]{7,8}+$/i", $value);
    }
```

## DBを利用したい
他のクラスと同様にコンストラクタインジェクションを利用することができますが、
DBが利用しやすいように用意されているものがあります。
dbのインスタンスは

```php
$this->app->singleton('validation.presence', function($app) {
    return new DatabasePresenceVerifier($app['db']);
});
```

としてコンテナに登録されていますので、

```php
    /**
     * @param string $connection
     * @return \Illuminate\Validation\DatabasePresenceVerifier
     */
    protected function resolveDbInstance($connection = "slave")
    {
        $dbPresence = $this->container['validation.presence'];
        $dbPresence->setConnection($connection);
        return $dbPresence;
    }

    public function validateJpZipCode($attribute, $value, $parameters)
    {
        $this->resolveDbInstance();
        // dbを利用したバリデート
    }
```
もしくは、DatabasePresenceVerifierを継承したクラスをコンテナに登録しなおしてもOKです
メソッド内でDBファサードや、Eloquentを利用することもできますが、
テストはしっかりと書くなどをしましょう。

他にもたくさんtipsはありますが、ソースコードを読みながら実装しやすい方法を見つけてみましょう！