Introduction
Laravel, the popular PHP framework, offers a multitude of features that streamline the process of developing robust web applications. Among these features, Understanding Accessors and Mutators stands out as a vital component. In this article, we will explore these concepts in depth, breaking them down into 25 comprehensive sections to ensure you grasp the essence of Accessors and Mutators in Laravel.
What is an Accessor?
When an Eloquent attribute value is accessed, an accessor modifies it. Make a protected method on your model to represent the accessible attribute in order to define an accessor. When applicable, the method name should match the “camel case” representation of the actual underlying model property or database field.
We’ll define an accessor for the first_name attribute in this example. Eloquent will automatically invoke the accessor when it tries to get the value of the first_name attribute. Every attribute accessor and mutator method needs to specify the Illuminate\Database\Eloquent\Casts\Attribute return type-hint:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get the user's first name.
*/
protected function firstName(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
);
}
}
An Attribute instance that specifies how the attribute will be accessed and, optionally, changed is returned by all accessor methods. We are merely specifying the attribute’s access method in this case. To achieve this, we provide the get argument to the constructor of the Attribute class.
As you can see, the accessor receives the column’s initial value and uses it to change and return the value. You may easily retrieve the value of the accessor by accessing a model instance’s first_name attribute:
use App\Models\User;
$user = User::find(1);
$firstName = $user->first_name;
Building Value Objects From Multiple Attributes
It may occasionally be necessary for your accessor to combine several model characteristics into a single “value object”. In order to accomplish this, your get closure might take a second parameter of $attributes, which will be sent to it automatically and include an array containing every attribute that the model currently has:
use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;
/**
* Interact with the user's address.
*/
protected function address(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes) => new Address(
$attributes['address_line_one'],
$attributes['address_line_two'],
),
);
}
Accessor Caching
Any modifications made to value objects returned from accessors will automatically be synchronized back to the model prior to the model being saved. Eloquent keeps track of instances returned by accessors, enabling it to return the same instance each time the accessor is called, which makes this possible:
use App\Models\User;
$user = User::find(1);
$user->address->lineOne = 'Updated Address Line 1 Value';
$user->address->lineTwo = 'Updated Address Line 2 Value';
$user->save();
But occasionally, especially for computationally demanding primitive values like strings and booleans, you might want to enable caching for them. When defining your accessor, you can use the shouldCache method to do this:
protected function hash(): Attribute
{
return Attribute::make(
get: fn (string $value) => bcrypt(gzuncompress($value)),
)->shouldCache();
}
When defining an attribute, you can use the withoutObjectCaching method to override the object caching behavior of the attribute:
/**
* Interact with the user's address.
*/
protected function address(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes) => new Address(
$attributes['address_line_one'],
$attributes['address_line_two'],
),
)->withoutObjectCaching();
}
What is a Mutator?
When an Eloquent attribute value is set, a mutator modifies it. You can create your attribute with the set argument, which will define a mutator. Let us define a mutator for the attribute called first_name. When we try to change the value of the first_name attribute on the model, this mutator will be triggered automatically:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Interact with the user's first name.
*/
protected function firstName(): Attribute
{
return Attribute::make(
get: fn (string $value) => ucfirst($value),
set: fn (string $value) => strtolower($value),
);
}
}
The value being set on the attribute will be received by the mutator closure, enabling you to work with the value and return the altered value. All we have to do to utilize our mutator on an Eloquent model is set the first_name attribute:
use App\Models\User;
$user = User::find(1);
$user->first_name = 'Sally';
The set callback in this case will be invoked with the value Sally. Subsequently, the mutator will utilize the strtolower function on the name and assign the resultant value to the internal $attributes array of the model.
Mutating Multiple Attributes
Occasionally, it can be necessary for your mutator to set several characteristics on the underlying model. You may accomplish this by having the set closure return an array. Every key in the array ought to match a database column or underlying attribute connected to the model:
use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;
/**
* Interact with the user's address.
*/
protected function address(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes) => new Address(
$attributes['address_line_one'],
$attributes['address_line_two'],
),
set: fn (Address $value) => [
'address_line_one' => $value->lineOne,
'address_line_two' => $value->lineTwo,
],
);
}
Here we understand how Accessor and Mutator in Laravel