Search code examples
phplaravelormeloquentlaravel-artisan

Laravel eloquent unguarded during seeding


I am experiencing a strange problem with eloquent.

I've got two eloquent models, a User model and a Profile model which both have separate tables and have a one to one relationship.

I have a single form where the data for both of these models is passed into. So data belonging to User and Profile is present in the form.

I tried to perform the inserts as cleanly as possible from the controller:

$user = $this->users->createUser($request->all());

the createUser function is defined like this

public function createUser(array $data)
{
    $user = new User($data);
    // set some non relevant user options here...
    $user->save();

    $profile = new Profile($data);
    $user->profile()->save($profile);

    return $user;
}

But this produces a sql error, because eloquent assigns all the values from the form to both models, so User and Profile both get all the same properties, so eloquent tries to insert into columns that don't exist for the given model.

I thought that the $fillable field was used to prevent this sort of thing from happening, but I do have the $fillable fields defined in both models:

// User
protected $fillable = ['email', 'password', ...];

// Profile
protected $fillable = ['first_name', 'last_name', ...];

I've also tried this: (new User)->fill($data) but that doesn't work, nor should it since the fill method is also called from the Model constructor.

In my mind this is not the expected behaviour, but then again, I may have misunderstood the concept of fillable. Any insight would be appreciated. I know this can be solved by declaring each key I want to send to the constructor, but that seems very messy to me. Also, I am using laravel v5.2.29

EDIT

Upon further testing I've come to the conclusion that this only happens inside when I run the database seeders, where I do use these functions. Digging deeper, I see that this makes perfect sense, since running the seed command "unguards" the base Model class. So this is expected behaviour.

My question is, is there any way to override this default, so that the models don't get "unguarded" when running the seed command, so that I can use my helper functions to populate my test database, or do I have to correctly define each data record manually inside the seeder classes?


Solution

  • Yes, from some version of Laravel 5.2 when using seeding models are unguarded as you noticed. In fact it's quite reasonable it's unguarded, but if you want to change it, well it's possible but not so simple.

    Here are steps you should do to achieve that:

    1. Comment in config/app.php line Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, add add line with your custom ConsoleSupportServiceProvider class.

    2. Create this class same as original ConsoleSupportServiceProvider (actually you can extend it) but in $providers property change Illuminate\Database\SeedServiceProvider into custom one

    3. Again create custom SeedServiceProvider that extends original SeedServiceProvider and now override registerSeedCommand to create again custom SeedCommand class

    4. Create custom SeedCommand class that extends original SeedCommand and now all you need is changing:

      public function fire()
      {
          if (! $this->confirmToProceed()) {
            return;
         }
      
         $this->resolver->setDefaultConnection($this->getDatabase());
      
         Model::unguarded(function () {
            $this->getSeeder()->run();
         });
      }
      

      into

      public function fire()
      {
          if (! $this->confirmToProceed()) {
            return;
         }
      
         $this->resolver->setDefaultConnection($this->getDatabase());
      
         $this->getSeeder()->run();
      
      }