Laravel: Why I gave up on $fillable

Mass-assignment is when you use an array to create/update eloquent models.

$user->update(['name' => 'Dan Gosling', 'email' => '', ...etc]) 

User::create(['name' => 'Jack Ba', 'email' => '', ...etc])

The $fillable and $guarded model properties are part of Laravel’s solution to avoid mass-assigning unwanted attributes when someone passes an unexpected HTTP parameter through a request and ends up changing a column in your database you did not expect.

While you can, and should protect yourself by filtering out the request parameters and only accept a known list using $request->only('param1', 'param2'), having protection at the model level is encouraged – you never know when some request somewhere slips through the cracks.

A lot of people  ditched $fillable for $guarded  because they grew tired of receiving mass-assignment exceptions whenever they were adding new columns without including them in the $fillable property.

An exception once in while did not bother me so I kept using $fillable. It was a nice place to keep ~all my model attributes, making it easier to find what that model has to offer. I could have grabbed them from the migration files, but as projects evolve and new migration files are created, it becomes harder and harder to keep track of what columns were added or removed.

$fillable made it easier, but I was using it for the wrong reason. I wasn’t using it as a whitelist, but more as just an attribute list. The biggest downside was that I was pushing myself away from using mutators. I was avoiding them so I wouldn’t pollute the sanctity of the attribute list.

Having a $car->dealership_name mutator and no dealership_name attribute felt weird as in “are you even a real db column? No you’re not!”. Plus, what if someone thinks that’s a db column and tries to use it in a create/update statement?

You couldn’t tell which ones are columns or mutators without screening the entire model file. So I decide to not use mutators and go with method calls $car->dealershipName(). This way I knew I was calling something custom and not a real db column.

Using $fillable brought other problems:

  • the list kept growing, cluttering the model. 5-10 are ok, but sometimes I had 30 – from which only 2 or 3 needed to be protected (a.k.a guarded)
  • mixing up attributes with method calls felt weird.
<div>{{ $user->name }}</div>
<div>{{ $user->job_title }}</div>
<div>{{ $user->dealershipName() }}</div> // yuck!
<div>{{ $user->phone_number }}</div>

This, and the constant peer pressure around me made me abandon $fillabe and give $guarded a chance. And so far it’s great!

Not only I’m not getting any annoying mass-assignment exceptions, but I also stoped worrying about having to garden a never-ending list of attributes. The guilt of using mutators is gone and I can make full use of their power. The only thing left is getting used to check attributes using sequel pro…

Subscribe to get my latest blog posts.