The Biggest Problem with Eloquent Accessors “Magic”

I confess – during five years of working with Laravel, I’ve been fascinated by its “magic” and how little code is needed to actually make things work. But recently, as my projects grew in size, that “magic” started to become a problem. One of the typical example is Eloquent Accessors, so in this article I will put an argument against using them, and what to do instead.


How Accessors Work and Why They’re Great

Let’s remember, how accessors work. Imagine that in users DB table you have columns name and surname, but in your table you want to view them as one – as full name. But you don’t want to do that string concatenation every time, so accessors are a great solution!

You just define this in app/User.php:

public function getFullNameAttribute()
{
    return $this->name . ' ' . $this->surname;
}

And then, whenever you have $user object, you can write this, for example, in Blade:

{{ $user->full_name }}

Eloquent will take care of calling this method, also converting full_name snake case into getFullNameAttribute camel case. Magic, isn’t it?


Problem 1: What is This Field?

The problems start usually in bigger projects and bigger teams, when the code is taken over by someone else.

For example, when you have a lot of fields listed in the table, or somewhere else, and you don’t obviously see that certain field is an accessor. For example, if you see $user->full_name and $user->home_address nearby, someone would probably expect that both are DB columns, right?

So, accessors hurt readability of the code. It gets even worse if a junior developer (or someone less familiar with Laravel) works on this part of the code – it may take a lot of time for them to understand, why this field doesn’t exist in the database, and they may spend minutes/hours digging in the past migrations, looking for full_name column.

Also, these fields are not “clickable” in your IDE, so it’s not easy to find what that method is really hiding inside. You need to search all project (or model file) for “getFullName” in your IDE like PhpStorm.


Problem 2: Bigger Accessors May “Hide” Bugs

Here we have a simple example of string concatenation. But quite often I see accessors as full-blown calculation methods with 20+ lines of code logic.

Imagine that this full_name would have bigger logic of international names – in some countries there are three parts of names, also you may include “Mr/Mrs/Ms”, initials, lower/upper case letters and much more. While it still works, there’s a good chance of bugs in that big logic.

And it’s pretty rare that someone would actually write automatic tests for the accessors. If you look from code structure point of view, such bigger calculations should be some kind of a Service class, which would be easier to test by Unit Tests.


Solution: Just Use “Getter” Methods

So, what to do to avoid too much “magic”? Simple – just use methods as methods, without any converting to anything.

So, instead of {{ $user->full_name }} in Blade, wouldn’t it be more readable to have this? {{ $user->getFullName() }}.

First, it’s immediately understood that it’s not a DB column – with () at the end, it’s clear that it’s a method. Also, your IDE will allow you to click on it, and land inside of that method in your Model. Easier navigation, huh?

In the model, just rename getFullNameAttribute() to getFullName():

public function getFullName()
{
    return $this->name . ' ' . $this->surname;
}

Do you agree? Have you ever encountered the problem with “too much magic” in Laravel – with accessors, or other magic methods?

Like our articles?
Check out our Laravel online courses!

11 COMMENTS

        • Oh right, good argument.
          Personally, for APIs I’m used to Resources class where I define each field individually, so auto including in json for accessors was never an issue for me.

          • Yeah sorry for the mess. So you suggest typing every field by hand when you use resources. Doesnt make this the framework useless? I mean it should do something megically. That is why we develop libraries think.

  1. It depends. I personally do that in resources, yes, because I want to know exactly what my API returns, so hide some fields, maybe not expose ID etc. But that’s more of a personal preference, generally that auto-casting is a good argument for using Accessors.

  2. I think I’ve read somewhere, but then I couldn’t find it again… that Accessors are cached. I.e., instead of doeint his:
    “`
    public function getCalculatedProp()
    {
    if (! isset($this->calculatedProp)) {
    $this->calculatedProp = $this->doGetCalculatedProp();
    }

    return $this->calculatedProp;
    }
    protected function doGetCalculatedProp()
    {
    // some heavy calculation goes here;

    return $res;
    }
    “`

    … instead of that, you just put the heavy calculation into the ::getCalculatedPropAttribute() and it does runtime caching for you.

    NOW, THE IMPORTANT: For quite some time I was sure it was true… but recently I did a test: put a `dump(‘here’)` into accessor’s body and addressed this accessor several time in tinker. And it dumped every time! So, I’m now confused =)

  3. I think for big projects/teams it’s better to stay away from framework specific magical features when coding and play readability first, this certainly will help the team to expand/change with out much time lost on adapting new comers to framework specific magic (of course unless the feature is very clear, readable and not magical).

  4. If you’re using Laravel you should be familiar with accessors and mutators. If another team comes in, they should be familiar with them too.

    As soon as you see a model attribute you know that it might be a “virtual” one. Leverage PHPDoc to make it clear if you want. We want/need this imo.

    If you’ve got complexe logic, isolate it somewhere else: dedicated function(s) on the model, service class, trait, whatever suits you.

    But Laravel is nice and let you use whatever you like really.

LEAVE A REPLY

Please enter your comment!
Please enter your name here