[Laravel/Eloquent] Why you should avoid using Accessors?

sethsandaru

Seth Phat

Posted on August 20, 2022

[Laravel/Eloquent] Why you should avoid using Accessors?

Hi Laravel developers,

As you may know, Laravel's Eloquent provides you a fancy way to create a "getter" and transform/access the data as how you want. Example:

// App/Models/User 

public function getFullNameAttribute(): string
{
    return sprintf('%s %s', $this->first_name, $this->last_name);
}

// Seth Phat
$user->full_name;
Enter fullscreen mode Exit fullscreen mode

Looks cool, isn't it?

But, you have to avoid it as soon as you can, here are the reasons.

1/ Conflicts between the table's columns

As the example above, we can't tell the different between an accessor and a table's column. It is too similar how we access the data.

When your projects grow, more and more features come to join, overusing the accessors will create a big pain in your codebase:

  • You can't tell the different between the columns and the accessors in your code.
  • Maintenance and tech debt will rise high.
  • Confuse new people when they're reading the code, logic,...
  • You have to open a SQL Manager (eg TablePlus) all the time to see what is it.
  • ...

2/ Maintain phpDoc manually

The more accessors you have, the higher time to maintain the phpDoc of your Model class.

To make it IDE-friendly, code suggestions,... you have to add your accessors into the phpDoc of your class, example:

/**
* @property-read string $full_name // Accessor
* @property-read string $sex // Accessor
*/
Enter fullscreen mode Exit fullscreen mode

Imagine you created 10 accessors, you have to add it 10 times. Or when you don't need it anymore, you need remove both the accessor and the doc attribute.

Not so fun, IKR?

3/ Hard to mock

When TDD comes to play, you want to write test. You want to mock your Eloquent model. The mocking process itself is a big pain.

Eloquent will check what attribute wanna access then return the data via __get() magic method. And to mock:

$user = $this->createMock(User::class):
$user->expects($this->exactly(3))
    ->method('__get')
    ->willReturnOnConsecutiveCalls(
        'Seth Phat', // full_name
        'Male', // sex
        // ... more and more
     );
Enter fullscreen mode Exit fullscreen mode

And I believe nobody would like that mocking process above. It is super hard to maintain.

So what should I use?

A good'ol getter method is simply perfect. IDE-friendly, no need to maintain phpDoc, super easy to mock:

public function getFullName(): string
{
    return sprintf('%s %s', $this->first_name, $this->last_name);
}
Enter fullscreen mode Exit fullscreen mode

Mock:

$user = $this->createMock(User::class):
$user->expects($this->once())
    ->method('getFullName')
    ->willReturn('Seth Phat');
// mock more...
Enter fullscreen mode Exit fullscreen mode

With that mocking process, write test is totally enjoyable and easy to maintain.

Limitation

  • Can't use appends & hidden
    • Alternatively: you can use Laravel's Resource response and preferred to use this way instead of return a whole Eloquent Model

Final

That's my experience for this one. How about you guys?

Cheers!

💖 💪 🙅 🚩
sethsandaru
Seth Phat

Posted on August 20, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related