Framework: [5.5] Model instanced passed to observer (created method) in not refreshed

Created on 16 Feb 2018  Â·  4Comments  Â·  Source: laravel/framework

  • Laravel Version: 5.5.34
  • PHP Version: 7.1.7 (Homestead)
  • Database Driver & Version: MySQL 5.7.18 (Homestead)

Description:

In my MySQL table, I have some columns with default values. If I create new model (with Model::create($attributes), the unfilled attributes are retrieved from database (in the model that create method returns).

I have a observer for the model, and when I call Model::create($attributes), the fields with default in database and not provided, are not in the instance passed to the observer's created method.

Steps To Reproduce:

I have a observer with this code:

class MasterObserver
{
    public function created(Master $master)
    {
        Log::info('Observer. ' . $master->toJson());
        $master->refresh();
        Log::info('Observer. ' . $master->toJson());
        dispatch(new SyncSlavesJob($master));
    }
}

I gets this lines printed in the logs:

[2018-02-16 13:52:24] dev.INFO: Observer. {"protocol":"https","host":"example.com","key_password":"secret","is_blocked":false,"id":2}  
[2018-02-16 13:52:24] dev.INFO: Observer. {"id":2,"name":"example.com","host":"example.com","protocol":"https","port":4085,"key_password":"secret","is_blocked":false}

In the not refreshed instance, the attributes not provided aren't filled, but ID is filled, that means the ID was received from database on creation.

It is the normal behavior or is a bug?

All 4 comments

Hi @montyclt i suppose its normal behavior.

Anytime 'create' is used, the returned model only contains the attributes provided. This avoids another call to the database. If you need any default values that are set by the db, then you'll need to refresh before using.
As you can see in the below code, the new model is instantiated with the attributes provided and that is what is returned after it is saved.

    public function create(array $attributes = [])
    {
        return tap($this->newModelInstance($attributes), function ($instance) {
            $instance->save();
        });
    }

You can define your default attributes directly on the model, mirroring your database defaults:

protected $attributes = [
  'language' => 'en',
  'timezone' => 'Solar/Mars',
  …
];

Two effects of this:

  • you've to manually keep them in sync
  • this attributes will actively be used when saving the model, too and the DB defaults will basically not be used. I you don't keep them in sync you may be surprised.

See also this example tinker session:

>>> DB::listen(function ($q) { var_dump($q->sql); });
=> null
>>> $u = \App\Models\User::create(['email' => 'daz']);
string(84) "insert into "users" ("email", "modified", "created") values (?, ?, ?) returning "id""
=> App\Models\User {#1440
     email: "daz",
   }

Now add default attributes in a new tinker session:

>>> $u = \App\Models\User::create(['email' => 'baz']);
string(237) "insert into "users" ("out_of_office", "language", "email", "modified", "created") values (?, ?, ?, ?, ?) returning "id""
=> App\Models\User {#1451
     language: "en",
     out_of_office: false,
     email: "baz",
   }
>>> 

@mfn @devcircus Thanks you for explanations. New thing learned today ;)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kerbylav picture kerbylav  Â·  3Comments

PhiloNL picture PhiloNL  Â·  3Comments

gabriellimo picture gabriellimo  Â·  3Comments

fideloper picture fideloper  Â·  3Comments

RomainSauvaire picture RomainSauvaire  Â·  3Comments