3

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();
});

2 Answers 2

2

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' => []
 ]);
Sign up to request clarification or add additional context in comments.

Comments

0

In addition to dparoli's outstanding answer, it is also possible to add a default value through Laravel's boot method, which is available on every Model.

Something like the following example code

   protected static function boot()
   {
      parent::boot(); //because we want the parent boot to be run as well
      static::creating(function($model){
         $model->propertyName = 'propertyValue';
      });
    }

You can play with this approach if you like as well.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.