💁♂️
Laravel で json 型のカラムを持つ Eloquent Model でやっておいた方が良いこと
概要
Laravel で json 型のカラムを持つ Eloquent Model を操作する時に、
「型安全に書ければこの修正不要だったなぁ(Laravel がよしなにやってくれたら良いのに...)」
と感じた事に関する記事です。
本題
先に結論
setAttribute
を明示的に使用した方が良い。
json 型のカラムの取得
json 型のカラムを持つ場合、 $casts
に json を指定するのはよくある事だと思います。
protected $casts = [
'json_column' => 'json',
...
こうする事で、以下のようにアクセスする事が可能になります。
$hoge = $hoge->json_column['hoge'];
かゆい所に手が届かず...
このような Eloquent Model を操作する場合、 json 型のカラムなので連想配列を保存することになると思うのですが、配列以外にも文字列とかも保存できてしまいます。
// これがエラーにならない
$hoge->json_column = 'json';
$hoge->save();
気をつければ問題ない話ですが、もしも間違えてセットした時にエラーになってくれないのはかなりストレスです。
そもそもなぜエラーにならないのかというと、 Eloquent Model に値をセットする時は以下のような経路を辿って処理されます。
まず、json へキャスト可能かどうかの判定をクリアします。
Illuminate/Database/Eloquent/Concerns/HasAttributes.php
public function setAttribute($key, $value)
{
// 〜省略〜
// 下記 if 文を通る
if (! is_null($value) && $this->isJsonCastable($key)) {
$value = $this->castAttributeAsJson($key, $value);
}
// 〜省略〜
次に、 json にキャストされるのですが、
Illuminate/Database/Eloquent/Concerns/HasAttributes.php
protected function castAttributeAsJson($key, $value)
{
$value = $this->asJson($value);
if ($value === false) {
throw JsonEncodingException::forAttribute(
$this, $key, json_last_error_msg()
);
}
return $value;
}
単に json_encode
が実行されるだけとなっています。
よって、配列以外でも保存できてしまいます。
Illuminate/Database/Eloquent/Concerns/HasAttributes.php
protected function asJson($value)
{
return json_encode($value);
}
解決策
これを解決するために、明示的に attributes にセットする時の型を指定させました。
public function setJsonColumnAttribute(array $jsonColumn): void
{
$this->attributes['json_column'] = json_encode($jsonColumn);
}
少し面倒ですがこれによって、配列以外は保存しようとするとエラーになってくれるので、型安全にコードを書くことができます。
良かった良かった。
Discussion