My Database Had “john SMITH”, “Sara ahmed”, and “MUHAMMAD Ali” Until I Fixed This One Thing

I was building a small CRM for a local real estate agency, and after a few weeks of real use, I checked the customers table just to browse the data. It was a mess. Names stored as “john SMITH”, “Sara Ahmed”, “MUHAMMAD Ali”, all depending on however each agent happened to type it into the form that day. Phone numbers were equally inconsistent, some with dashes, some with spaces, some just a plain string of digits.
My first instinct was writing a cleanup script to fix the existing data, which I did, but that didn’t stop the same mess from happening again the very next day when agents kept typing names however they wanted. I needed something that automatically formatted this data consistently, every single time, regardless of how it was typed in.
That’s exactly what led me to properly use Eloquent accessors and mutators, something I’d technically known existed but had never really used seriously before that project. Once I did, it fixed the problem permanently, and I’ve used the same pattern on basically every project since.
What Accessors and Mutators Actually Do (No Jargon)
Think of a mutator as a filter that runs automatically whenever you’re about to save data. Someone types “john SMITH,” the mutator quietly fixes it to “John Smith” before it ever touches the database.
An accessor works the opposite direction, it’s a filter that runs whenever you’re reading data back out. Maybe you stored a price as 150000 (in the smallest currency unit) but want to display it as a properly formatted “Rs. 150,000” whenever it’s shown.
Neither of these change what’s actually stored versus what’s displayed unless you specifically want them to, they just give you one central place to control formatting instead of repeating the same formatting logic in every single controller or Blade view across your app.
Step 1: Fixing the Name Formatting Problem
For newer Laravel versions (9 and up), accessors and mutators use a cleaner syntax through the Attribute class, which is what I’d recommend using now over the older getXAttribute() / setXAttribute() method naming convention.
use Illuminate\Database\Eloquent\Casts\Attribute;
class Customer extends Model
{
protected function name(): Attribute
{
return Attribute::make(
set: fn ($value) => ucwords(strtolower($value)),
);
}
}
That set closure is the mutator, it runs automatically whenever you assign a value to $customer->name, before it saves to the database.
$customer = new Customer();
$customer->name = 'john SMITH';
$customer->save();
// Stored in database as "John Smith"
No matter how an agent types a name into the form, whether they use all caps, no caps, or a random mix, it gets normalized to proper case automatically before saving. This one change alone fixed the messy data problem going forward, permanently, without needing agents to remember any formatting rules themselves.
Step 2: Formatting Phone Numbers Consistently
Same idea, different field. Agents were entering phone numbers in every possible format you can imagine, “0300-1234567”, “03001234567”, “0300 123 4567”. I wanted them all stored the same clean way, digits only, then displayed with proper formatting whenever shown.
protected function phone(): Attribute
{
return Attribute::make(
set: fn ($value) => preg_replace('/[^0-9]/', '', $value),
get: fn ($value) => substr($value, 0, 4) . '-' . substr($value, 4),
);
}
The mutator (set) strips out anything that isn’t a digit before saving, so “0300-1234567” and “0300 123 4567” both get stored identically as “03001234567”. The accessor (get) then formats it back into a readable “0300-1234567” style whenever you actually access $customer->phone in your code or Blade views.
$customer->phone = '0300 123 4567';
$customer->save();
// Stored as: 03001234567
echo $customer->phone;
// Displays as: 0300-1234567
This was genuinely satisfying to implement, since it meant every phone number displayed across the entire CRM, customer lists, individual profiles, exported reports, automatically looked consistent, without me having to remember to format it manually in a dozen different places.
Step 3: A Real Pricing Example (Where I Actually Made a Mistake)
On a separate project, a small online store, I stored product prices as integers representing the smallest currency unit to avoid floating point rounding issues, a common recommendation for handling money in code. So Rs. 1,500 gets stored as 150000 (in paisa, the smallest unit), similarly, a price in Indian Rupees would follow the same smallest-unit logic if the store expanded to serve Indian customers too.
I initially built the accessor like this:
protected function price(): Attribute
{
return Attribute::make(
get: fn ($value) => 'Rs. ' . number_format($value / 100, 2),
);
}
This worked fine for displaying prices, but I made a mistake when I later needed to actually use the raw numeric price value for a calculation, applying a discount percentage, for example. I forgot the accessor was now returning a formatted string like “Rs. 1,500.00” instead of a raw number, and my discount calculation broke completely, since you can’t do math on a string with currency symbols and commas in it.
// This broke
$discountedPrice = $product->price * 0.9; // price is now a formatted string!
The fix was separating concerns properly, keeping the raw stored value accessible separately from the formatted display value:
protected function formattedPrice(): Attribute
{
return Attribute::make(
get: fn () => 'Rs. ' . number_format($this->price / 100, 2),
);
}
Now $product->price stays a raw, usable integer for any calculations, while $product->formatted_price gives you the nicely formatted display version whenever you actually need to show it to a customer. Lesson learned: don’t overwrite the raw value’s accessor with a formatted string if you’ll ever need the raw number for actual calculations elsewhere in your code.
Step 4: Combining Get and Set for a Genuinely Useful Real Example
Here’s one from that same real estate CRM that combines both directions nicely, handling a property listing’s slug (the URL-friendly version of its title).
protected function title(): Attribute
{
return Attribute::make(
set: function ($value) {
return [
'title' => $value,
'slug' => \Str::slug($value),
];
},
);
}
This one’s slightly different since it’s setting two actual database columns from a single assignment. Whenever an agent typed a property title like “3 Bedroom House in DHA Phase 5”, it automatically generated and saved a clean slug like “3-bedroom-house-in-dha-phase-5” alongside it, without the agent needing to think about URLs at all.
$property->title = '3 Bedroom House in DHA Phase 5';
$property->save();
// title column: "3 Bedroom House in DHA Phase 5"
// slug column: "3-bedroom-house-in-dha-phase-5"
Step 5: A Genuinely Useful Accessor for Computed Values
Not every accessor needs to touch a mutator at all. Sometimes you just want a computed value that doesn’t map to any actual database column directly. On the CRM project, I needed to show how long ago a customer was added, “3 days ago,” “2 months ago,” that kind of thing.
protected function joinedAgo(): Attribute
{
return Attribute::make(
get: fn () => $this->created_at->diffForHumans(),
);
}
echo $customer->joined_ago;
// "3 days ago"
This isn’t stored anywhere, it’s calculated fresh every time you access it, purely for display convenience. Genuinely handy for keeping this kind of formatting logic out of Blade views and controllers, centralizing it on the model itself instead.
Common Mistakes I’d Warn You About
Overwriting a raw numeric value’s accessor with a formatted string. This is the mistake I made with pricing. If you’ll ever need the raw value for calculations, keep a separate accessor specifically for the formatted display version instead.
Forgetting mutators run every time you save, not just once. If your mutator logic is expensive (like calling an external API to validate something), be mindful it’ll run on every single save, not just the initial creation.
Not testing with genuinely messy real-world input. My initial testing during development used clean, properly formatted names and phone numbers I typed myself. The actual mess only showed up once real agents used the actual form with their own inconsistent typing habits. Test with genuinely messy input, extra spaces, mixed case, unexpected characters, before assuming your accessor or mutator handles every real-world scenario.
Putting too much logic directly in the Attribute closure for complex cases. For anything beyond a simple one-liner, I’ll extract the logic into a separate protected method on the model instead, keeping the Attribute definition clean and readable:
protected function phone(): Attribute
{
return Attribute::make(
set: fn ($value) => $this->formatPhoneForStorage($value),
get: fn ($value) => $this->formatPhoneForDisplay($value),
);
}
protected function formatPhoneForStorage($value)
{
return preg_replace('/[^0-9]/', '', $value);
}
protected function formatPhoneForDisplay($value)
{
return substr($value, 0, 4) . '-' . substr($value, 4);
}
Using the old syntax and new syntax inconsistently across a project. If you’re on Laravel 9+, stick with the Attribute::make() syntax consistently rather than mixing it with the older getXAttribute() style across different models in the same project, purely for readability and consistency for anyone else working on the codebase later.
Real Use Cases Beyond These Examples
Since learning this properly, I’ve used accessors and mutators for:
- Automatically capitalizing and trimming whitespace from user-submitted business names on a directory listing site
- Converting stored temperature values between Celsius and Fahrenheit depending on a user’s regional preference, on a small weather tracking tool
- Masking sensitive data like partial credit card numbers for display purposes, showing only the last 4 digits, on a payment records admin panel
- Automatically generating initials from a full name for avatar placeholders, on a team management dashboard
Final Thoughts
That messy customers table taught me something that seems obvious in hindsight, don’t rely on people typing things consistently, build your models to handle inconsistency automatically instead. Accessors and mutators are exactly the tool for this, and once I started using them properly, so many small formatting headaches across different projects just disappeared entirely.
If you’ve got formatting logic scattered across multiple controllers or Blade views right now, name capitalization, phone number formatting, currency display, anything like that, it’s worth pulling that logic into an accessor or mutator on the model itself instead. Future you, and anyone else working on that codebase later, will genuinely thank you for it.



