MUGIJIRU.JP

Webエンジニアの雑談ブログ

Laravel5.3 以降 バージョンアップ MIddreware ConvertEmptyStringsToNullと日付系Validation

表題の件です。

アップグレードガイド 5.3 Laravel

配列、論理型、整数、数値、文字列をバリデートする場合、新しいnullableルールを指定していない限り、有効な数値として判断されなくなりました。

結論から言うと
Laravel5.3以降はバリデーションルールのrequireの逆は指定なしではなくnullable
という話です。

Laravel5.5のデフォルトのグローバルミドルウェア構成

下記のとおりです。

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\TrustProxies::class,
    ];
...

ConvertEmptyStringsToNull とは何をしているミドルウェアなのか

読んだところ、リクエストパラメータが文字列かつカラ文字だったらnullに変換する処理ということがわかります。

<?php

namespace Illuminate\Foundation\Http\Middleware;

class ConvertEmptyStringsToNull extends TransformsRequest
{
    /**
     * Transform the given value.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return mixed
     */
    protected function transform($key, $value)
    {
        return is_string($value) && $value === '' ? null : $value;
    }
}

Validationにおいて何が起こるのか

現在確認できている限り、日付系のバリデーションルールにおいて
常にfalseを返す動きを取るようになってしまうことが分かっています。

他のルールでも影響があるかもしれませんが全ては追っていません。

日付の範囲をフォームから受け取る、こんなバリデーションルールがあったとします。

<?php
$rules = [
    'date-from' => 'required|date',
    'date-to'   => 'date|after:date-from'
];

本来であれば

  • date_fromというパラメータは必須で、日付形式である必要がある。
  • date_toというパラメータは省略可能であるが、入力がある場合は日付形式かつdate_from以降である必要がある。

という動きをするはずです。

ですが、このConvertEmptyStringsToNullというミドルウェアが挟まると
afterのルールが入力の有無に関わらず常にfalseとなってしまいます!

なぜか

このConvertEmptyStringToNullミドルウェアを通ると

?date-from=2018-06-20&date-to=

というパラメータが

<?php
[
    'date-from' => '2018-06-20',
    'date-to'   => ''
];

ではなく

<?php
[
    'date-from' => '2018-06-20',
    'date-to'   => NULL
];

に変換されます。

すると、日付形式のバリデーション「compareDates」を利用するルールにおいては
値にNullが入っていると常にfalseを返すようになってしまいます。

<?php
# Illuminate/Validation/Concerns/ValidatesAttributes
...
    /**
     * Compare a given date against another using an operator.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @param  array  $parameters
     * @param  string  $operator
     * @return bool
     */
    protected function compareDates($attribute, $value, $parameters, $operator)
    {
        if (! is_string($value) && ! is_numeric($value) && ! $value instanceof DateTimeInterface) {
            return false;
        }

        if ($format = $this->getDateFormat($attribute)) {
            return $this->checkDateTimeOrder(
                $format, $value, $this->getValue($parameters[0]) ?: $parameters[0], $operator
            );
        }

        if (! $date = $this->getDateTimestamp($parameters[0])) {
            $date = $this->getDateTimestamp($this->getValue($parameters[0]));
        }

        return $this->compare($this->getDateTimestamp($value), $date, $operator);
    }

Laravel5.3以降は、このミドルウェアによって入力のないパラメータはNullとなり
DB更新と親和性が高まります。
パラメータの存在有無は、従来通りhasで確認することになるでしょう。
その代わり、requiredの逆にnullableを指定しないと、一部のルールが常にfalseを返すようになります。

Laravel5.2以前の動きに戻したい場合、このミドルウェアコメントアウトすれば元の挙動に戻ります。

バージョンアップ中に当現象に遭遇し、ソースを読んで変な時間を取ることになったので
似たケースで困る方の助けになれば幸いです。