Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Eloquent casting attributes to Collections unexpected behaviour

Q1. I have an Eloquent model that casts an attribute to a Collection. Calling Collection's method on this attribute doesn't affect the model values. Eg: put()

When using Collections , iam able to do this :

$var = collect();
$var->put('ip', '127.0.0.1');
var_dump($var);

Output as expected :

object(Illuminate\Support\Collection)[191] protected 'items' => array (size=1) 'ip' => string '127.0.0.1' (length=4)

But when i use with a casted attribute on a Eloquent model, this doesn't work as expected

$user = App\User::create(['email'=>'Name', 'email'=>'[email protected]', 'password'=>bcrypt('1234')]);
$user->properties = collect();
$user->properties->put('ip', '127.0.0.1');
var_dump($user->properties);

object(Illuminate\Support\Collection)[201] protected 'items' => array (size=0) empty

This doesn't populate the field. I think that another collection is created, so to work as expected i must assign this new collection to my field.

Like so : $user->properties = $user->properties->put('ip', '127.0.0.1');

Q2. Is there a proper way to initialize collection of the field by default (create an empty collection if the field is null), without having to call $user->properties = collect(); "manually" every time?


User.php

class User extends Authenticatable
{
    protected $casts = [
        'properties' => 'collection',
    ];
    ...
}

Migration file

Schema::table('users', function($table) {
    $table->text('properties')->nullable();
});
like image 478
Jean Celestin Avatar asked Nov 25 '25 01:11

Jean Celestin


1 Answers

Q1: an attribute casted to collection has a getter that returns, each time, a new BaseCollection that is constructed on the value of the attribute.

As already supposed the getter returns another collection instance and every direct change on it does not change the value of the attribute but instead the newly created collection object.

As also pointed by you the only way to set a a collection casted attribute is to assign it his own original value merged with new ones.

So instead of put() you have to use:

$user->properties = $user->properties->put('ip', '127.0.0.1');
// or
$user->properties = $user->properties ->merge(['ip'=>'127.0.0.1'])

Q2: We have to think that the database representation is a text; so IMHO the proper way to initialize a Model in the migration is to give it a default empty json, i.e.:

$table->text('properties')->default('{}');

But this works only for models created without setting the property field and retrieved after.

For a newly created Model my advice is to pass a default void array, i.e.:

 App\User::create([
     'name'=>'Name', 
     'email'=>'[email protected]', 
     'password'=>bcrypt('1234'),
     'properties' => []
 ]);
like image 120
dparoli Avatar answered Nov 28 '25 02:11

dparoli



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!