laravel
Eloquent
laravel5.5

LaravelのEloquentでJSON型の列を取得する

LaravelのEloquentで列の型がJSON型の列を取得するときにちょっとハマったので調査しました。

$post = Post::find(1);
echo $post->json_column->col1;  // エラー
echo $post->json_column['col1'] // エラーにならない

TL;DR

結論からいうとJSON型の列を取得する場合は
モデルクラスの$castsにjsonであることを明示しておき、
$model->json_column['property']というように連想配列でアクセスすれば取得できます。

環境

名前 バージョン
PHP 7.1.6
Laravel 5.5.14

配列でのアクセスになるのはなぜか

モデルは以下のとおり。

Models/Post.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model as Eloquent;

class Post extends Eloquent
{
    use \Illuminate\Database\Eloquent\SoftDeletes;

    protected $casts = [
        'json_column' => 'json',  // ★これ
    ];

    protected $fillable = [
        'json_column',
    ];
}

$castsをjsonにしているのでjson_decodeされた結果が返ってくると思ったので
プロパティアクセス($post->json_column->col1)でいけると思ったのですがエラーになります。
配列でアクセス($post->json_column['col1'])するとエラーになりません。

ひょっとしてjson_decode($string, true)で連想配列にしてjsonをパースしてるのではと思ってソースを見てみました。

Illuminate\Database\Eloquent\Concerns\HasAttributes.php
    /**
     * Cast an attribute to a native PHP type.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return mixed
     */
    protected function castAttribute($key, $value)
    {
        if (is_null($value)) {
            return $value;
        }

        switch ($this->getCastType($key)) {
            case 'int':
            case 'integer':
                return (int) $value;
            case 'real':
            case 'float':
            case 'double':
                return (float) $value;
            case 'string':
                return (string) $value;
            case 'bool':
            case 'boolean':
                return (bool) $value;
            case 'object':
                return $this->fromJson($value, true);
            case 'array':
            case 'json':
                return $this->fromJson($value);    // ★ここ
            case 'collection':
                return new BaseCollection($this->fromJson($value));
            case 'date':
                return $this->asDate($value);
            case 'datetime':
                return $this->asDateTime($value);
            case 'timestamp':
                return $this->asTimestamp($value);
            default:
                return $value;
        }
    }

    /**
     * Decode the given JSON back into an array or object.
     *
     * @param  string  $value
     * @param  bool  $asObject
     * @return mixed
     */
    public function fromJson($value, $asObject = false)
    {
        return json_decode($value, ! $asObject); // ★ここでjsonを連想配列にしている
    }

json_decodeの第2引数をtrue、つまりassocがtrueなので連想配列形式で返却されます。
モデルクラスのプロパティで$castsをjsonにしていた場合は連想配列でキャストされたものが返却されます。

参考

PHP.net json_decode