LaravelPHP

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

What is Accessor? 

Accessors create a dummy attribute on the object which you can access as if it were a database column. So if your database has a user table and it has FirstName and LastName column and you need to get a full name then it will be like.

What is Mutator?

Mutator is used to set the value of the attribute, A mutator transforms an Eloquent attribute value when it is set.

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
    );
}

I hope it will help.

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button