Improved single-method accessors and mutators in Laravel 8.x

Amit Merchant · December 27, 2021 ·

When you want to format certain Eloquent models before setting/retrieving in Laravel, you would certainly reach for the accessors and mutators.

I have already discussed this in detail in one of the posts. But if I have to give a summary of this, I’ll explain it with an example.

Old Accessors & Mutators

So, let’s say we have a field called tax in the orders table and if we want to set the computed tax on the field, we would need to define a mutator with the set{Foo}Attribute name format in the model like so.

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'orders';

    public function setTaxAttribute($value)
    {
        return ($value * 20)/100;
    }
}

So, from now on, the tax field will get saved with the computed value.

$order = App\Order::find(5);

$order->tax = 15; // tax in percentage
// will be saved as "3"

The same goes for when you want to retrieve the raw tax value of an order, you would need to define a accessor with the get{Foo}Attribute name format in the model like so.

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'orders';

    public function getTaxAttribute($value)
    {
        return ($value * 100)/20;
    }
}

This way, when the tax is retrieved for an order, it will be the raw tax (without all the computation).

$order = App\Inventory::find(5);

$orderTax = $order->tax; 
// retrieved back as 15

The problem

The is the standard way of defining accessors and mutators in Laravel. But the problem with this approach is that it’s not very elegant since this needs two different methods for retrieving and setting model fields.

Also, according Taylor, this is not the “style” the Laravel as a framework follows throughout.

So, he came up with a solution that would reduce this operation to a single method that would make this feature more seamless.

Improved Accessors & Mutator

The recent release of Laravel now comes with an alternate way of defining accessors/mutators, all with a single method.

As per the PR, the framework now comes with an Illuminate\Database\Eloquent\Casts\Attribute return type that lets you define attribute access/mutation behavior in a single method.

So, if we want to rewrite the previous example with this approach, here’s how we can do it.


namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;

class Order extends Model
{
    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'orders';

    /**
     * Get the order tax.
     */
    protected function tax(): Attribute
    {
        return new Attribute(
            fn ($value) => ($value * 20)/100, // accessor
            fn ($value) => ($value * 100)/20, // mutator
        );
    }
}

As you can tell, you can directly define a method of the same name as the field. In our case, it’s the tax method.

From this method, you can then return the Attribute type. Where the Attribute constructor accepts two callables as arguments.

  • The first argument is a callable where you can define logic for the accessor.
  • The second argument is a callable where you can define the logic to mutate/set the field value.

The example uses the shorter arrow method syntax but if you have a business logic that can’t be done in a single line, you can write it using the regular Closures like so.

protected function tax(): Attribute
{
    return new Attribute(
        function($value) {
            return ($value * 20)/100; // raw tax
        },
        function($value) {
            return ($value * 100)/20, // computed tax
        } 
    );
}

It’s even better with PHP 8

Now, if you’re on PHP 8+, this syntax is even sweeter and readable when you’re using it with the named parameters.

protected function tax(): Attribute
{
    return new Attribute(
        get: fn ($value) => ($value * 20)/100, // raw tax
        set: fn ($value) => ($value * 100)/20, // computed tax
    );
}
Learn the fundamentals of PHP 8 (and 8.1), the latest version of PHP, and how to use it today with my new book PHP 8 in a Nutshell. It's a no-fluff and easy-to-read guide to the latest features and nitty-gritty details of PHP 8. So, if you're looking for a quick and easy way to PHP 8, this is the book for you.

👋 Hi there! I'm Amit. I write articles about all things web development. If you like what I write and want me to continue doing the same, I would like you buy me some coffees. I'd highly appreciate that. Cheers!

Comments?