【Laravel livewire】livewireが空の文字列フィールドをトリミングしてくれないのでtraitで対処


どうしてそんな仕様なんだい。

Laravelには、リクエストで飛んできた空の文字列フィールドをnullに変換するミドルウェアが
デフォで発動するようになっています

デフォルトでは、LaravelはアプリケーションのグローバルミドルウェアスタックにApp\Http\Middleware\TrimStringsとApp\Http\Middleware\ConvertEmptyStringsToNullミドルウェアを含めています。これらのミドルウェアは、App\Http\Kernelクラスによってグローバルミドルウェアスタックにリストされています。これらのミドルウェアは、リクエストに応じてすべての受信文字列フィールドを自動的にトリミングし、 空の文字列フィールドをnullに変換します 。これにより、ルートとコントローラでのこれらの正規化について心配する必要がなくなります。

参照:Laravel 8.x HTTP Requests

Livewireはトリミングしてくれません。開発者も意図した仕様のようです。
この仕様について議論されているissueがありました
Empty nullable numeric field raises mysql exception #1950

「integer()->nullable()のカラムに空の値で更新しようとしたらエラーが出やがった!!何故トリミングしてくれない仕様なんだ?理由を教えてくれよ」

『Laravelのトリム文字列ミドルウェアを使うとネットワークリクエストレベルで変更されるんだ。
チェックサムを使用してデータを保存してるからエラーが起きちゃうんだ』
『後Alpine.jsとの連携でhogehuga.....』

みたいな感じです。

解決策1 ミューテターを使う

モデルへ属性の値を設定しようとすると自動的に処理が動いてくれる凄い奴です。

参照:[Laravel] 空文字をDB登録時にnullにしたい
最近書いた過去記事です。以前はこちらで対処しました。

解決策2 Traitを使う

以前に「これはバグでは」と立てられたissueです。
TrimStrings and ConvertEmptyStringsToNull middlewares do not work on Livewire #823

その際に『このTraitを使えばいけるぜ!』と有志が作成していたTraitを共有してくれていたので、一例として引用させてもらいます。

<?php

namespace App\Http\Livewire;

trait TrimAndNullEmptyStrings
{
    public function updatedTrimAndNullEmptyStrings($name, $value)
    {
        if (is_string($value)) {
            $value = trim($value);
            $value = $value === '' ? null : $value;

            data_set($this, $name, $value);
        }
    }
}
<?php

namespace App\Http\Livewire\Orders;

use App\Http\Livewire\TrimAndNullEmptyStrings;
use App\Models\Order;
use Livewire\Component;

class CreateOrder extends Component
{
    use TrimAndNullEmptyStrings;
}

メソッド名がupdatedTrimAndNullEmptyStrings?? 全ての各フィールドの値変更時に動くのはupdatedメソッドのハズだぜ?
何でupdatedTrimAndNullEmptyStringsが全てのフィールドの変更に発動するんだ?

という疑問を抱くと思います。

trait内でupdatedというメソッド名を書いてしまうと、livewire内でupdatedを書けなくなってしまうので、
Livewireはtrait使用時にメソッド名が重複しない様に構文を定期称してくれているのです。
この場合、updated + trait名 と命名する事でlivewire内のupdatedの後に実行してくれます。


参照:Livewire 2.x トレイト

おわりに

traitでやるのがベストなのかなぁと思います。
他に良い手段があれば教えて頂きたいです。