Improved single-method accessors and mutators in Laravel 8.x
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
);
}
Like this article?
Buy me a coffee👋 Hi there! I'm Amit. I write articles about all things web development. You can become a sponsor on my blog to help me continue my writing journey and get your brand in front of thousands of eyes.