The Problem With Accessors and Mutators In Laravel

Dallin Coons
4 min readJul 21, 2019

The Laravel framework feels magical in large part because it heavily uses PHP’s “magic methods” to add many conveniences. As a quick example, when you access name from the User model:

$user->name

the User class probably doesn’t actually have a name property defined, but through the magic of __get(), Laravel checks whethername is a relationship or a database value and returns the appropriate result.

However, during this process, Laravel also calls a method named hasGetMutator, which checks for a mutator method on the model:

if ($this->hasGetMutator($key)) {
return $this->mutateAttribute($key, $value);
}
public function hasGetMutator($key)
{
return method_exists($this,'get'.Str::studly($key).'Attribute');
}
protected function mutateAttribute($key, $value)
{
return $this->{'get'.Str::studly($key).'Attribute'}($value);
}

This means if you defined a getFullNameAttribute method on the User model, this method will be used to generate the returned value. This is called an accessor.

Accessors can be very convenient for cases where maybe you want to call $user->full_name without actually having a full_name column on the user table. I believe that these accessor methods should be avoided for the most part and here’s why:

Accessors often look exactly like relationships and attribute usage.

When reading code, it’s not uncommon to see code such as$podcast->subscriber, which could refer to a relationship or an attribute, but it’s not clear just from the code itself. This actually doesn’t end up being much of a problem, because it’s usually easy to differentiate between the two from the context. $podcast->subsriber is probably a relationship, and $podcast->subscriber_name is probably an attribute.

However, accessors add a third possibility into the mix, and they aren’t nearly as easy to differentiate from normal attributes. $podcast->subscriber_name could just as well be an accessor attribute.

Accessors are not cached

Calling attributes and relationships multiple times returns the value from cache, but accessors aren’t cached. As a result, you could accidentally be hurting your performance. Most developers are used to Laravels caching mechanism and have no qualms of calling attributes and relationships multiple times in the same section of code rather than assigning the results to a variable.

Because accessors look the same as attributes and relationships, you or members of your team might be inadvertently making multiple unnecessary calls to a database, for example. This is an unfortunate inconsistency.

Accessors case can be inconsistent

Notice that behind the scenes, when calling the accessor Laravel is converting the key to StudlyCaps.

$this->{'get'.Str::studly($key).'Attribute'}($value);

This means that $user->fullName works just as well as $user->full_name . Why is this a problem? Let’s say you want to remove an accessor method. Usually, this entails searching the app for any usages of it. There’s no guarantee you will find them all unless everyone on your team past and present have been perfectly consistent in their naming schemes.

New team members often come with different habits than how your team operates, such as calling attributes with camel case instead of snake case, or vice versa. You can set up a style guide for your team, but why add one more thing they need to enforce in code review, and one more habit you need to break in new members of your team?

Accessors have poor IDE support

Any modern-day IDE has the ability to “click-through” to a method, or to search for a method in a class. This is a small convenience that can save you a tremendous amount of time in the long run.

I don’t know of any IDE that supports clicking through to accessors, at least out of the box. What you’ll often have to do instead is search your model classes for getWhateverAttribute . Your IDE also can’t read the return value from the accessor method, which means you lose out on the IDE’s ability to help you avoid errors which would otherwise be obvious.

The Alternative — Bring Back the Getter

Wouldn’t it be nice if you could tell either from the code or the context where you would expect to find the definition of the attribute or relationship, and not interrupt your train of thought to go search the model to find out where that value is actually coming from?

My preference is to keep it incredibly simple. Just use a simple getter. Instead of $user->full_name , change it to $user->fullName() or $user->getFullName() and the problems I mentioned all but disappear. It’s now clear that this is not a database attribute, won’t be cached, it’s easy to find in the Model (in fact you can click right through to the definition of that method with any decent IDE), and you don’t need to worry about inconsistent word casing.

A Word About Mutators

I didn’t forget about mutators. Instead of:

public function setFirstNameAttribute($value)
{
$this->attributes['first_name'] = ltrim($value);
}

why not just use a setter function?

public function setName($value)
{
$this->attributes['first_name'] = ltrim($value);
}

With the mutator, it’s not so obvious that $user->name = 'Buzz' goes through the mutator method. With a setter function, it’s transparent that the value is going through a method instead of being set directly on the model.

The benefits of accessors

The biggest benefit I see from using accessor methods is the ability to ‘append’ to the array/JSON representations of your models.

You can get the best of both worlds by simply calling your accessors as getters, for example for an accessor method such getFullNameAttribute just call it directly: $user->getFullNameAttribute().

Another alternative I see to this is overriding the `toArray` method of the model and adding attributes. This could be done by reading `$appends` or another property and dynamically calling a getter on the model.

--

--