Laravel で json をDBに保存した時にUnicodeエスケープされる
Eloquent での json の使い方
(環境: PHP 8.1以上、Laravel 10)
LaravelのEloquent(Model) で json を扱う時は、モデルに以下のように記述と思います。
入出力する際に json を encode/decode することなく扱えます。
casts 便利ですね。
罠
非常に便利ですが、罠があります。
環境によって unicode にエスケープされたりされなかったりするのです。
どういう時困るか
実はPHPで通常利用する上では特に困りません。
しかしDB検索時の条件に指定する時などに困ります。
たとえば files テーブルの tags というカラムに配列でタグを入れていくという仕様になっていたとします。
そしてそのタグを条件に検索したい時、以下のようなSQLを発行すると思います。
select * files where tags LIKE "%景色%";
ところが、DB上では \u666f\u8272
というようにユニコードエスケープされた状態で保存されているので、文字列が不一致として認識されます。
これだとタグ検索ができません。
解法1: 検索する時にUnicodeエスケープする
シンプルです。
しかし環境によってエスケープされないこともあるので、両方に対応するコードを記述する必要があります。
File::where(function ($query) {
$query->where('tags', 'LIKE', '%' . $escaped . '%')
->orWhere('tags', 'LIKE', '%' . $unescaped . '%');
});
環境によって不要なコードが含まれているので冗長ですし、DBに無駄な負荷がかかります。
また php は標準で ユニコードにエスケープする関数がないので、自前で用意しなければなりません。
解法2: json_encode 時にエスケープしない
Eloquent で json を cast しているのは Illuminate\Database\Eloquent\Casts\Json
というクラスです。
この中で通常は json_encode
が呼び出されています。
public static function encode(mixed $value): mixed
{
return isset(static::$encoder) ? (static::$encoder)($value) : json_encode($value);
}
コードをよく見ると static::$encoder
があれば、それを用いてエンコード処理してくれるので、どうやらここを使えばうまくいけそうです。
さて json_encode
は第2引数でフラグを設定することができます。
そして JSON_UNESCAPED_UNICODE
という、名前の通りユニコードのエスケープをさせないためのフラグが定義されています。
これらを用い App\Providers\AppServiceProvider
の boot
関数内で以下のように記述してあげます。
public function boot(): void
{
Json::encodeUsing(fn ($value) => json_encode($value, JSON_UNESCAPED_UNICODE));
}
これで DB に保存する時にマルチバイトがユニコードエスケープされないようになります。
Discussion