Laravel5.2で、正しい年月か?をvalidateしようとして理不尽なことになったのでメモ。
やりたいこと
Laravel5.2で、年月のフォーマットだけでなく、存在する年月かをValidateする。
いきさつ
Laravel5.2の使用可能なバリデーションルールによると、「日付」「日付形式」の2パターンが利用出来る。
が。ですよ。
$validator = Validator::make($input, [
'from' => 'bail|required|date_format:Ym|before:"now"',
'to' => 'bail|required|date_format:Ym|before:"now"|after:from',
]);
if ($validator->fails()) {
throw new MyValidatorException();
}
のように日付形式をチェックするよう書いてやって、
from=201514
to=201520
とパラメータを渡してテストしてもエラーにならない!?
中を見てみる
/**
* Validate that an attribute matches a date format.
*
* @param string $attribute
* @param mixed $value
* @param array $parameters
* @return bool
*/
protected function validateDateFormat($attribute, $value, $parameters)
{
$this->requireParameterCount(1, $parameters, 'date_format');
if (! is_string($value) && ! is_numeric($value)) {
return false;
}
$parsed = date_parse_from_format($parameters[0], $value);
return $parsed['error_count'] === 0 && $parsed['warning_count'] === 0;
}
のように、「指定された日付フォーマット」として分割できるか?だけチェックして、それぞれの正当性をチェックしてくれない。
さっきの、"201520"を渡してデバッグすると、
っていうありさまである。
month=20が存在するかはチェックされない。
参考までに、「日付」の場合こんなことをしている。
/**
* Validate that an attribute is a valid date.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
protected function validateDate($attribute, $value)
{
if ($value instanceof DateTime) {
return true;
}
if ((! is_string($value) && ! is_numeric($value)) || strtotime($value) === false) {
return false;
}
$date = date_parse($value);
return checkdate($date['month'], $date['day'], $date['year']);
}
カスタムバリデーションの作成
Validatorクラスを自作します。
※requireParameterCount()はIlluminate\Validation\Validator.phpから引っ張ってきただけなので消してもオK。
<?php
namespace Myapp\Services\Validator;
class Validator {
/**
* Validate that an attribute is a valid year month.
*
* @param string $attribute
* @param string $parameters 日付パラメータ
* @param mixed $value 検証値
* @return bool
*/
public function validateYearMonth($attribute, $value, $parameters)
{
$this->requireParameterCount(1, $parameters, 'date_ym');
if (! is_string($value) && ! is_numeric($value)) {
return false;
}
$parsed = date_parse_from_format($parameters[0], $value);
return checkdate($parsed['month'], 1, $parsed['year']);
}
protected function requireParameterCount($count, $parameters, $rule)
{
if (count($parameters) < $count) {
throw new InvalidArgumentException("Validation rule $rule requires at least $count parameters.");
}
}
}
アプリに登録します。
<?php
namespace Myapp\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
\Validator::extend(
'date_ym', 'Myapp\Services\Validator\Validator@validateYearMonth'
);
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
}
}
Controllerで検証内容を定義します。
$validator = Validator::make($input, [
'from' => 'bail|required|date_ym:Ym|before:"now"',
'to' => 'bail|required|date_ym:Ym|before:"now"|after:from',
]);
if ($validator->fails()) {
throw new MyValidatorException();
}
以上で、意図した通り、パラメータに"201520"のような不正な年月が指定されてもエラーになりました。
とりあえずここまで。
追記)
ちなみに、before:"now"が期待通りに行きませんね。。。。
参考
https://readouble.com/laravel/5.2/ja/validation.html#available-validation-rules
https://readouble.com/laravel/5.2/ja/validation.html#custom-validation-rules
https://blog.e2info.co.jp/2016/06/05/laravel_custom_validation/