1

I am using Ardent and I faced strange behaviour of ignoring $fillable list while inserting/updating related models. I have the following models defined:

class User extends LaravelBook\Ardent\Ardent
{
    protected $table = 'users';

    public static $relationsData = [
        'contacts' => [self::HAS_MANY, 'Contact'],
    ];    
}

class Contact extends LaravelBook\Ardent\Ardent 
{
    protected $table    = 'user_contacts';
    protected $guarded  = ['*'];
    protected $fillable = [
        'user_id',
        'type',
        'value'
    ];

    public static $relationsData = [
         'user' => [self::BELONGS_TO, 'User'],
    ];
}

Now I am trying to add new contact to user:

    $user->contacts()->create([
    'type' => 'some type',
    'value' => 'some value',
    'unknown_field' => 'unknown value'
]);

... and I got SQL insert error:

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'unknown_field' in 'field list'     (SQL: insert into `user_contacts` (`type`, `value`, `unknown_field`, `user_id`, `updated_at`, `created_at`) values (?, ?, ?, ?, ?, ?)) (Bindings: array ( 0 => 'some type', 1 => 'some value', 2 => 'unknown value', 3 => 2, 4 => '1384854899', 5 => '1384854899', ))

In the same time this is working fine:

UserContact::create([
    'user_id'       => 2,
    'type'          => 'some type',
    'value'         => 'some value',
    'unknown_field' => 'unknown value'
]); 

I didn't get any SQL errors and 'unknown_field' was just ignored.

Any ideas why $fillable fields could be ignored while working via builder?!

6 Answers6

3

I don't understand why the HasManyOrOne relationship intentionally ignores fillable. It seems really counter intuitive. Either way, I think this should work for you.

$user->contacts()->save(Contact::create([ ... ]));
Collin James
  • 9,062
  • 2
  • 28
  • 36
1

It seems I found the reason of this behaviour. This is explicitly implemented in HasOneOrMany abstract class.

abstract class HasOneOrMany extends Relation {

    ...

    /**
     * Create a new instance of the related model.
     *
     * @param  array  $attributes
     * @return \Illuminate\Database\Eloquent\Model
     */
    public function create(array $attributes)
    {
        $foreign = array(
            $this->getPlainForeignKey() => $this->parent->getKey()
        );

        // Here we will set the raw attributes to avoid hitting the "fill" method so
        // that we do not have to worry about a mass accessor rules blocking sets
        // on the models. Otherwise, some of these attributes will not get set.
        $instance = $this->related->newInstance();

        $instance->setRawAttributes(array_merge($attributes, $foreign));

        $instance->save();

        return $instance;
    }

    ...
}

I am still looking for the suffitient solution to control this behaviour.

0

As stated in the offical documentation:

To get started, set the fillable or guarded properties on your model.

You have set both. You should remove the following line: protected $guarded = ['*'];

Ronald Hulshof
  • 1,986
  • 16
  • 22
  • From my point of view it doesn't matter. protected $guarded = array('*'); defined in Model which extended by Ardent. Any child will have it by default. I was trying with no $guarded defined in Contact but I got the same result. –  Nov 19 '13 at 14:33
0

Fortunately this will be fixed in version 4.2: https://github.com/laravel/framework/pull/2846

Added to all this, you can also filter the attributes manually:

$input = [
    'user_id'       => 2,
    'type'          => 'some type',
    'value'         => 'some value',
    'unknown_field' => 'unknown value'
];

$fillable = $user->contacts()->getRelated()->fillableFromArray($input);

$user->contacts()->create($fillable);

Keeping in mind that the example are using Eloquent\Model\fillableFromArray() method, which is protected, so it will be necessary, for example, replicate it:

class BaseModel extends Eloquent 
{
    public function fillableFromArray(array $attributes)
    {
        return parent::fillableFromArray($attributes);
    }
}
seus
  • 661
  • 10
  • 11
0

Use protected $guarded = array(); instead of protected $guarded = ['*'];

by using [*] you're telling laravel to guard all entities from autohydration / mass assignment!

array() sets this $guarded list to null.

The fillable property specifies which attributes should be mass-assignable. This can be set at the class or instance level.

The inverse of fillable is guarded, and serves as a "black-list" instead of a "white-list":

Read more at Laravel documentation on mass assignment

Community
  • 1
  • 1
Vishwajeet Vatharkar
  • 1,146
  • 4
  • 18
  • 42
0

The update methods are not on the model level, and won't respect the $fillable fields.

You could filter the input data by using Input::only['fillable fields here']

HotRod
  • 126
  • 4