Laravelにおける空文字の扱い
Laravelでは5.4以降、リクエスト内の空文字をnullに変換するようになりました。以前、Laravel 5.3でとあるシステムの開発を行っており、開発途中で新にリリースされた5.4に更新したところ、このnull化のあたりでハマりました。その時のメモです。
まず、空文字のnull変換はConvertEmptyStringsToNullミドルウェアで処理しています。5.4からこのミドルウェアが登録されたため、空文字がnullに変換されるようになりました。このため、app/Http/Kernel.phpの$middlewareからConvertEmptyStringsToNullを削除してしまえば、元の動作に戻すことはできます。
当時、ConvertEmptyStringsToNullを削除してしまおうとも考えたのですが、Laravelがそのような作り変更したのであれば、それに則った作りにした方がいいと判断し、ConvertEmptyStringsToNullはそのままにしてアプリケーション側を修正することにしました。
空文字がnullに変換されるようになったことで、多くの場合、以下の問題が発生することになると思います。
(1) データベースへの保存時
フォーム上のテキストボックス等の入力を省略した時に、従来はデータベースに空文字を設定していましたが、NULLを設定しようとします。このため、データベース側のカラムがNOT NULL属性だと保存時にエラーとなります。
このため、値が存在しない時にNULL値を格納するのが適切なカラムであればNULLを受け付けるカラムに変更したり、そうでなければリクエスト内のnull値を空文字に戻す処理が必要になります。
私は各フォームのフォームリクエストクラス内で、入力がなかった時のデフォルト値を定義するようにし、入力が省略された項目に対してデフォルト値を設定してからデータベースに保存するようにしています(実装の詳細は省きますが)。
デフォルト値の定義例
class XXXRequest extends FormRequest { ... protected $defaultValues = [ 'page_title' => '', 'page_body' => '', 'page_published' => null, ]; ...
(2) 省略可能項目のバリデーションルール指定
5.4以前では例えば数字を受け付ける必須項目の場合は'required|numeric'のようにルールを指定し、省略可能項目の場合は'numeric'のように'required'を省略した形で指定していました。
例1: Laravel 5.4以前での省略可能項目の指定
必須項目: 'required|numeric' 省略可能項目: 'numeric'
しかし、5.4以降では'numeric'の指定だと入力を省略した時に「nullはnumericでない」と判断されてエラーとなってしまいます。5.4以降では省略可能項目には以下のように'nullable'を指定する必要があります。
例2: Laravel 5.4以降での省略可能項目の指定
必須項目: 'required|numeric' 省略可能項目: 'nullable|numeric' ← 'numeric'だとnullがnumericでないと判断されエラーになる
これはバリデーション時、入力値が空文字の場合、「入力が省略された」とみなしrules(上の例1では'numeric')を適用しないのですが、入力値がnullだと「nullが入力された」とみなし'numeric'ルールを適用してしまうためです(詳細は*1参照)。
このため、入力を省略してもバリデーションが実行されエラーになってしまいます。これを防ぐため、省略可能項目ではnullableを指定しておく必要があります。
ConvertEmptyStringsToNullに関連して発生した問題は以上です。システム内の全てのフォームで見直しが必要になるので、運用中のシステムを5.4以降にアップグレードする場合は、ConvertEmptyStringsToNullを削除してしまう方が安全かもしれません。
ConvertEmptyStringsToNullの追加に関しては結構インパクトのある変更だと思うのですが、https://readouble.com/laravel/5.4/ja/upgrade.htmlの5.4へのアップグレードガイドにはなぜか記述がありません。当時、nullの扱いに悩んだのでその時のまとめでした。
[参考]
(*1) このあたりのrulesを適用するしないの制御はvendor/laravel/framework/src/Illuminate/Validation/Validator.phpのpresentOrRuleIsImplicit()で行っています。入力が空文字だった場合、$implicitRulesに指定されているruleのみ実行し、その他は無視するようになっています。
$implicitRules = [ 'Required', 'Filled', 'RequiredWith', 'RequiredWithAll', 'RequiredWithout', 'RequiredWithoutAll', 'RequiredIf', 'RequiredUnless', 'Accepted', 'Present', ];
$implicitRulesにはrequred系のルールが並んでいるので、空文字が入力された場合、入力の必須チェックだけが実行されることになります。
このため、空文字の場合、'required'での必須チェックは行われるが'numeric'によるチェックは行われません。一方、nullの場合、通常の入力として扱われるので、'numeric'のチェックが必ず行われます。このため、'nullable'がない'とnumeric'でエラーになります。
省略可能項目に対するnullable指定についてはhttps://readouble.com/laravel/5.4/ja/validation.html#a-note-on-optional-fieldsにも説明があります。
※このあたりの処理はLaravel 5.4, 5.5 LTSで確認しています。
投稿日:2019/05/14 14:24