Eloquent Accessors and Mutators — Attribute Transformation in Laravel
In this tutorial, you will learn about Eloquent Accessors and Mutators. We cover key concepts, practical examples, and best practices to help you master this topic.
Laravel Eloquent accessors transform attributes when reading, mutators modify them when writing, and casts handle automatic conversion between database and PHP types.
What You'll Learn
By the end of this tutorial, you'll define accessors and mutators, use attribute casting, create custom cast types, handle date casting, and apply encryption and array casting.
Why Accessors and Mutators Matter
Accessors and mutators keep attribute transformation in the model layer instead of duplicating logic in controllers and views.
Real-World Use
A User model casts password to hashed via mutator, serializes JSON settings via array cast, converts timestamps to Carbon, and uses a custom cast for money values.
Attribute Path
flowchart LR
A[Eloquent ORM] --> B[Accessors Mutators]
B --> C[Accessors]
B --> D[Mutators]
B --> E[Casts]
B --> F[Custom Casts]
B --> G{You Are Here}
style G fill:#f90,color:#fff
Accessors
Transform attribute values on read.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;
class User extends Model {
protected function fullName(): Attribute {
return Attribute::make(
get: fn(mixed $value, array $attributes) => trim(
($attributes["first_name"] ?? "") . " " . ($attributes["last_name"] ?? "")
),
);
}
protected function profileUrl(): Attribute {
return Attribute::make(
get: fn() => route("profile", $this->id),
);
}
}
// Usage
echo $user->full_name;
echo $user->profile_url;
Mutators
Modify attribute values on write.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Str;
class Post extends Model {
protected function title(): Attribute {
return Attribute::make(
set: fn(string $value) => [
"title" => $value,
"slug" => Str::slug($value),
],
);
}
protected function password(): Attribute {
return Attribute::make(
set: fn(string $value) => bcrypt($value),
);
}
}
// Usage
$post->title = "Hello World"; // Also sets slug to "hello-world"
$user->password = "secret"; // Stores bcrypt hash
Attribute Casting
Cast attributes to common types automatically.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Product extends Model {
protected $casts = [
"price" => "decimal:2",
"is_active" => "boolean",
"published_at" => "datetime:Y-m-d",
"metadata" => "array",
"config" => "json",
"options" => "object",
"encrypted_secret" => "encrypted",
];
}
// Usage
$product->published_at instanceof Carbon; // true
$product->is_active; // bool, not int
$product->metadata; // array
Custom Casts
Implement custom casting logic.
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class MoneyCast implements CastsAttributes {
public function get(Model $model, string $key, mixed $value, array $attributes): string {
return number_format($value / 100, 2);
}
public function set(Model $model, string $key, mixed $value, array $attributes): int {
return (int) round($value * 100);
}
}
// In model
protected $casts = ["amount" => MoneyCast::class];
Date Casting
Customize date Serialization with casting.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Event extends Model {
protected $casts = [
"starts_at" => "datetime:Y-m-d H:i",
"ends_at" => "datetime",
"registered_at" => "immutable_datetime",
"processed_at" => "datetime:c",
];
protected $dates = [
"created_at",
"updated_at",
"deleted_at",
];
}
Common Mistakes
1. Tight Coupling to Accessors
Accessors should transform values, not perform heavy queries. Keep business logic in services.
2. Forgetting $casts for Security
Non-cast fields can be mass-assigned with unexpected values. Always cast booleans and JSON.
3. Mutators Silently Modifying Input
Mutators that change values without notice cause confusion. Keep mutations predictable.
4. Using Accessors in Queries
Accessors run after fetching. You cannot filter by accessor values in the database query.
5. Over-Casting Performance
Every accessor call runs PHP code. Avoid expensive calculations in accessors being called in loops.
Practice Questions
1. What is the difference between an accessor and a mutator?
Accessors transform on read. Mutators transform on write.
2. How do you cast a JSON column to an array?
Add "column_name" => "array" to the $casts array.
3. What does "encrypted" cast do?
Automatically encrypts on save and decrypts on read using Laravel's encryption.
4. How do you create a custom cast class?
Implement the CastsAttributes interface with get() and set() methods.
5. Challenge: Create a custom slug cast.
<?php
class SlugCast implements CastsAttributes {
public function get(Model $model, string $key, mixed $value, array $attributes): string {
return $value;
}
public function set(Model $model, string $key, mixed $value, array $attributes): string {
return Str::slug($value);
}
}
FAQ
Mini Project: Product Model with Casting
Build a product model with comprehensive attribute handling.
<?php
namespace App\Models;
use App\Casts\MoneyCast;
class Product extends Model {
protected $casts = [
"price" => MoneyCast::class,
"compare_price" => MoneyCast::class,
"is_active" => "boolean",
"metadata" => "array",
"published_at" => "datetime",
];
protected function displayName(): Attribute {
return Attribute::make(get: fn() => sprintf("%s (%s)", $this->name, $this->sku));
}
}
What's Next
Eloquent Serialization Eloquent Eager Loading Eloquent Query Scopes
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro