Laravel Accessors & Mutators: Stop Fighting with Your Data ๐ฉโจ
Laravel Accessors & Mutators: Stop Fighting with Your Data ๐ฉโจ
Ever found yourself writing the same data transformation logic everywhere? Converting dates, formatting names, encrypting passwords? Yeah, me too. Until I discovered that Eloquent has been doing my job better than me all along!
Let me show you how accessors and mutators can make your code cleaner than a freshly deployed production server (you know, before users touch it).
What Are These Magic Things? ๐ฎ
Think of your Eloquent model as a butler:
- Mutators = Your butler quietly fixes data before storing it (you say "john doe", butler stores "John Doe")
- Accessors = Your butler presents data nicely when you retrieve it (database has "2026-02-05", you get "February 5, 2026")
In production systems I've built at Cubet, these little helpers saved us from writing the same transformation code in 50 different places. And when requirements changed? Changed it once. Done. โ
Real-World Problem: User Names ๐ค
The Scenario: Users enter their names like cavemen typing - "JOHN DOE", "jane doe", "bOb SmItH"
The Old Way (Pain):
// In your controller
$user = new User();
$user->name = ucwords(strtolower($request->name));
$user->save();
// In your API
return response()->json([
'name' => ucwords(strtolower($user->name))
]);
// In your view
<h1>{{ ucwords(strtolower($user->name)) }}</h1>
// ๐ญ Copy-paste everywhere!
The Elegant Way (Mutator):
// In your User model - ONE place!
protected function name(): Attribute
{
return Attribute::make(
get: fn ($value) => ucwords(strtolower($value)),
set: fn ($value) => strtolower($value),
);
}
// Everywhere else in your app:
$user->name = "JOHN DOE"; // Stores: "john doe"
echo $user->name; // Displays: "John Doe"
The magic: Write once, works everywhere. This is the way! ๐
Real Talk: Password Hashing ๐
As a Technical Lead who's audited countless security issues, I've seen developers forget to hash passwords in one place. Just ONE. And boom - plain text passwords in the database.
The Safe Way:
// In your User model
protected function password(): Attribute
{
return Attribute::make(
set: fn ($value) => bcrypt($value),
);
}
// Now this ALWAYS works correctly:
$user->password = 'secret123'; // Automatically hashed! ๐
Pro tip: Notice we only defined set here? You never read a password hash directly, so we skip the get accessor. Smart, right?
Money, Money, Money ๐ฐ
In a real e-commerce project I architected, we stored prices in cents (to avoid floating-point nightmares). But showing "$1999" to users? Not ideal!
// In your Product model
protected function price(): Attribute
{
return Attribute::make(
get: fn ($value) => $value / 100, // DB: 1999 โ App: 19.99
set: fn ($value) => $value * 100, // App: 19.99 โ DB: 1999
);
}
// Usage is beautiful:
$product->price = 29.99; // Stores: 2999 cents
echo $product->price; // Shows: 29.99 dollars
Why cents in the database? Because 19.99 + 0.01 sometimes equals 19.999999999997 in floating-point math. Ask me how I know... ๐
Date Formatting That Doesn't Suck ๐
Stop writing ->format('M d, Y') everywhere!
protected function createdAt(): Attribute
{
return Attribute::make(
get: fn ($value) => Carbon::parse($value)->format('M d, Y'),
);
}
// Now this works:
echo $post->created_at; // "Feb 05, 2026" automatically!
The "Virtual" Attribute Trick ๐ฏ
Want a full_name attribute that doesn't exist in your database? Accessors got you!
// In your User model
protected function fullName(): Attribute
{
return Attribute::make(
get: fn () => "{$this->first_name} {$this->last_name}",
);
}
// Now you have a "virtual" column:
echo $user->full_name; // "John Doe"
// And it even works in JSON responses!
return $user->toArray();
// ['first_name' => 'John', 'last_name' => 'Doe', 'full_name' => 'John Doe']
A pattern that saved us in a real project: We had separate street, city, state, zip fields but needed a formatted address everywhere. One accessor, problem solved!
Security: Automatic Data Sanitization ๐ก๏ธ
This is where my security background kicks in. Want to strip HTML tags from user input automatically?
protected function bio(): Attribute
{
return Attribute::make(
set: fn ($value) => strip_tags($value),
);
}
// Malicious input gets cleaned automatically:
$user->bio = '<script>alert("XSS")</script>Hello';
// Stores: "Hello" - Script? What script? ๐
Security Note: This isn't a replacement for proper validation and output escaping, but it's a solid defense-in-depth layer!
Bonus: The Appends Property ๐ฆ
Got accessors you want in JSON responses by default? Add them to $appends:
class User extends Model
{
protected $appends = ['full_name', 'avatar_url'];
protected function fullName(): Attribute
{
return Attribute::make(
get: fn () => "{$this->first_name} {$this->last_name}",
);
}
protected function avatarUrl(): Attribute
{
return Attribute::make(
get: fn () => "https://cdn.example.com/avatars/{$this->id}.jpg",
);
}
}
// Now User::find(1)->toJson() automatically includes these! ๐
The "Don't Overdo It" Warning โ ๏ธ
Look, I love accessors and mutators, but don't get drunk on power:
โ Bad: Complex calculations in accessors
// NO! This hits the database every time you access it!
protected function totalSpent(): Attribute
{
return Attribute::make(
get: fn () => $this->orders()->sum('total'),
);
}
โ Good: Simple transformations only
// YES! This is just formatting, no side effects
protected function email(): Attribute
{
return Attribute::make(
get: fn ($value) => strtolower($value),
);
}
Quick Reference Card ๐ด
When to use Mutators (set):
- Formatting data before storage
- Hashing/encrypting values
- Sanitizing user input
- Converting units (dollars to cents)
When to use Accessors (get):
- Formatting data for display
- Creating virtual attributes
- Converting units (cents to dollars)
- Combining multiple fields
When to use both:
- Round-trip transformations (encrypt/decrypt)
- Unit conversions (store in one format, display in another)
Real Talk: My Favorite Use Case ๐ฌ
In a serverless e-commerce backend I built, we used mutators to automatically encrypt sensitive customer data (addresses, phone numbers) on write, and accessors to decrypt on read. The entire team could work with plain data - encryption was invisible!
protected function phone(): Attribute
{
return Attribute::make(
get: fn ($value) => decrypt($value),
set: fn ($value) => encrypt($value),
);
}
Zero-knowledge encryption? Check. Developer-friendly API? Check. Security team happy? Check! โ
The Bottom Line
Accessors and mutators are like having a personal assistant for your data:
- Clean: Transformation logic in ONE place
- Consistent: Works everywhere automatically
- Maintainable: Change once, fixes everywhere
- Testable: Easy to unit test your model logic
Stop copy-pasting ucwords(strtolower($user->name)) everywhere like it's 2010. Let Eloquent do the boring work while you focus on building features that matter!
Questions about accessors? Hit me up on LinkedIn - I've probably made every mistake already! ๐
Want more Laravel magic? Check out my other posts on Eloquent relationships and scopes!
Now go clean up that transformation code! ๐งนโจ