Eloquent: Mutators
Introduction
Accessors and mutators allow you to transform Eloquent attribute values when you retrieve or set them on model instances. For example, you may want to encrypt a value while it is stored in the database and then automatically decrypt it when you access it on an Eloquent model.
Accessors
Accessors transform attribute values when you retrieve them. Define typed getter methods in your model:
class User extends Model {
// Raw attribute access
String? get rawName => getAttribute('name') as String?;
// Accessor with transformation
String get fullName {
final firstName = getAttribute('first_name') as String? ?? '';
final lastName = getAttribute('last_name') as String? ?? '';
return '$firstName $lastName'.trim();
}
// Accessor with formatting
String get formattedPhone {
final phone = getAttribute('phone') as String?;
if (phone == null) return '';
return '+1 (${phone.substring(0, 3)}) ${phone.substring(3, 6)}-${phone.substring(6)}';
}
}
Using Accessors
final user = await User.find(1);
print(user.fullName); // "John Doe"
print(user.formattedPhone); // "+1 (555) 123-4567"
Mutators
Mutators transform attribute values when you set them. Define setter methods:
class User extends Model {
// Raw setter
set name(String? value) => setAttribute('name', value);
// Mutator with transformation
set email(String? value) =>
setAttribute('email', value?.toLowerCase().trim());
// Mutator with hashing (example)
set password(String? value) =>
setAttribute('password', value != null ? hashPassword(value) : null);
}
Using Mutators
final user = User();
user.email = ' [email protected] ';
print(user.getAttribute('email')); // "[email protected]"
user.password = 'secret123';
// Stored as hashed value
Attribute Casting
The casts property provides automatic type conversion for attributes:
class Task extends Model {
@override
Map get casts => {
'is_completed': 'bool',
'priority': 'int',
'progress': 'double',
'due_date': 'datetime',
'settings': 'json',
};
// Typed accessors use the cast values
bool get isCompleted => getAttribute('is_completed') as bool? ?? false;
int get priority => getAttribute('priority') as int? ?? 0;
double get progress => getAttribute('progress') as double? ?? 0.0;
Carbon? get dueDate => getAttribute('due_date') as Carbon?;
Map? get settings =>
getAttribute('settings') as Map?;
}
Available Cast Types
| Cast | Returns | Database Type |
|---|---|---|
bool |
bool |
INTEGER (0/1) |
int |
int |
INTEGER |
double |
double |
REAL |
datetime |
Carbon |
TEXT (ISO 8601) |
json |
Map or List |
TEXT (JSON) |
Date Casting
Date attributes are automatically converted to Carbon instances:
class Event extends Model {
@override
Map get casts => {
'starts_at': 'datetime',
'ends_at': 'datetime',
'published_at': 'datetime',
};
Carbon? get startsAt => getAttribute('starts_at') as Carbon?;
Carbon? get endsAt => getAttribute('ends_at') as Carbon?;
Carbon? get publishedAt => getAttribute('published_at') as Carbon?;
// Duration helper
Duration? get duration {
if (startsAt == null || endsAt == null) return null;
return endsAt!.diff(startsAt!);
}
// Check if event is happening now
bool get isHappeningNow {
final now = Carbon.now();
return startsAt?.isBefore(now) == true &&
endsAt?.isAfter(now) == true;
}
}
Working with Dates
final event = await Event.find(1);
// Format dates
print(event.startsAt?.format('MMMM d, yyyy')); // "January 15, 2024"
// Human readable
print(event.startsAt?.diffForHumans()); // "in 2 days"
// Comparison
if (event.startsAt?.isFuture() == true) {
print('Upcoming event');
}
JSON Casting
JSON attributes are serialized/deserialized automatically:
class Monitor extends Model {
@override
Map get casts => {
'settings': 'json',
'tags': 'json',
};
// Access as Map
Map get settings =>
(getAttribute('settings') as Map?) ?? {};
// Access as List
List get tags =>
(getAttribute('tags') as List?)?.cast() ?? [];
// Setters
set settings(Map value) =>
setAttribute('settings', value);
set tags(List value) =>
setAttribute('tags', value);
}
Using JSON Attributes
final monitor = Monitor();
// Set complex data
monitor.settings = {
'timeout': 30,
'retries': 3,
'headers': {'Authorization': 'Bearer token'},
};
monitor.tags = ['production', 'critical', 'api'];
await monitor.save();
// Retrieve
print(monitor.settings['timeout']); // 30
print(monitor.tags.first); // "production"
[!TIP] Always define typed getters for your attributes to get IDE autocompletion and type safety throughout your application.