Framework: Unable to define $attributes for JSON column as an array

Created on 6 Jul 2018  路  6Comments  路  Source: laravel/framework

  • Laravel Version: 5.6
  • PHP Version: 7.2
  • Database Driver & Version: MySQL 5.7

Description:

When using JSON columns on a model we're unable to set default values in $attributes as an array as it will throw the following error on save.

PHP Warning:  json_decode() expects parameter 1 to be string, array given in /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php on line 682

You can however define them as a JSON string and it will save fine, but my expectation was to work with it as an array as I would throughout my codebase.

Steps To Reproduce:

  1. Add a JSON column to your User table.
  2. Define some default values for the column within App\User::$attributes.
  3. Add the column to App\User::$casts and ensure it casts to an array.
  4. Run tinker and run $u = new App\User(); $u->save() to produce the error.

Most helpful comment

The casting only happens when you explicitly set (or get) attribute values: $user->json_column = [...];

Does something like this help you?

class User extends Model {
    public function __construct(array $attributes = []) {
        parent::__construct($attributes + ['json_column' => ['default']]);
    }
}

All 6 comments

Do you mean a case like this?

class User extends Model {
    protected $attributes = [
        'json_column' => ['foo' => 'bar']
    ];
    protected $casts = [
        'json_column' => 'json'
    ];
}

Correct although $casts can be either json or array and it will fail in the same way.

Prior to getting more JSON support in eloquent it was recommended to cast JSON columns as an array and eloquent would just handle it as required.

I don't think you can expect this to work. The idea is that $attributes contains the raw and serialized values (in this case: JSON encoded). These are the values that you receive from the database and can also write into the database directly without any changes.

What's your use case? Do you use the default values when you create new model instances?

I was trying to define some default values for our JSON columns, but the fact you describe $attributes as containing raw and serialized values does match with what's happening.

It just wasn't expected as no comments in source mentioned this and I assumed that casting would treat it the same as an instantiated instance does.

The casting only happens when you explicitly set (or get) attribute values: $user->json_column = [...];

Does something like this help you?

class User extends Model {
    public function __construct(array $attributes = []) {
        parent::__construct($attributes + ['json_column' => ['default']]);
    }
}

Thanks @staudenmeir for clarifying this, $attributes is meant to be carrying the raw data coming from the DB and not any other forms.

Was this page helpful?
0 / 5 - 0 ratings