<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Hash;
class User extends Model
{
protected $fillable = ['first_name', 'last_name', 'email', 'password'];
protected $hidden = ['password'];
protected $appends = ['full_name', 'initials'];
// Modern accessor/mutator using Attribute
protected function password(): Attribute
{
return Attribute::make(
get: fn ($value) => $value, // No transformation on get
set: fn ($value) => Hash::make($value), // Hash on set
);
}
protected function email(): Attribute
{
return Attribute::make(
get: fn ($value) => strtolower($value),
set: fn ($value) => strtolower($value),
);
}
// Computed accessor
protected function fullName(): Attribute
{
return Attribute::make(
get: fn () => "{$this->first_name} {$this->last_name}",
);
}
protected function initials(): Attribute
{
return Attribute::make(
get: fn () => strtoupper(
substr($this->first_name, 0, 1) .
substr($this->last_name, 0, 1)
),
);
}
// Cast attributes
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'preferences' => 'array',
'is_admin' => 'boolean',
];
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
// Store price in cents, display in dollars
protected function price(): Attribute
{
return Attribute::make(
get: fn ($value) => $value / 100,
set: fn ($value) => $value * 100,
);
}
// Format phone numbers
protected function phone(): Attribute
{
return Attribute::make(
get: fn ($value) => $this->formatPhone($value),
set: fn ($value) => preg_replace('/[^0-9]/', '', $value),
);
}
// Sanitize HTML
protected function description(): Attribute
{
return Attribute::make(
get: fn ($value) => $value,
set: fn ($value) => strip_tags($value, '<p><br><strong><em>'),
);
}
private function formatPhone(string $phone): string
{
return preg_replace('/(d{3})(d{3})(d{4})/', '($1) $2-$3', $phone);
}
}
<?php
$user = User::create([
'first_name' => 'John',
'last_name' => 'Doe',
'email' => 'JOHN@EXAMPLE.COM', // Lowercased automatically
'password' => 'secret', // Hashed automatically
]);
echo $user->full_name; // "John Doe"
echo $user->initials; // "JD"
echo $user->email; // "john@example.com"
$product = new Product();
$product->price = 19.99; // Stored as 1999 cents
echo $product->price; // 19.99
$product->phone = '(555) 123-4567';
// Stored as '5551234567', retrieved as '(555) 123-4567'
Accessors and mutators transform model attributes when retrieving or setting values. Accessors format data for presentation—converting cents to dollars, concatenating names, or generating computed properties. Mutators normalize input—hashing passwords, sanitizing HTML, or formatting phone numbers. I use attribute casting for automatic type conversion—dates to Carbon, JSON to arrays, encrypted strings. The modern approach uses single methods with Attribute return types combining get/set logic. Accessors never appear in database queries or toArray() unless appended. Mutators run before saving, ensuring data consistency. This pattern keeps data transformation logic centralized in models rather than scattered across controllers.