Skip to content

Eloquent: Mutators

View raw .md

Accessors, mutators, and attribute casts let you transform Eloquent model values automatically as they are read from or written to the database, without changing the calling code.

On this page

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 lower-case an email address as it is set and format a phone number as it is read.

Accessors#

Accessors transform attribute values when you retrieve them. Define typed getter methods in your model:

dart
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#

dart
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:

dart
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#

dart
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:

dart
class Task extends Model {
  @override
  Map<String, String> get casts => const {
    '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<String, dynamic>? get settings =>
      getAttribute('settings') as Map<String, dynamic>?;
}

Available Cast Types#

Cast Returns
bool bool
int int
double double
datetime Carbon
json Map or List

Custom Casts (CastsAttributes)#

For domain types like enums or value objects, implement CastsAttributes<T> and register the instance in casts:

dart
enum MonitorStatus { active, paused, failed }

class Monitor extends Model {
  @override
  Map<String, dynamic> get casts => const {
    'status': EnumCast(MonitorStatus.values),         // enum round-trip
    'statuses': ListCast(EnumCast(MonitorStatus.values)), // list of enums
    'created_at': 'datetime',                         // built-ins still work
  };

  MonitorStatus? get status => getAttribute('status') as MonitorStatus?;
  set status(MonitorStatus? value) => setAttribute('status', value);
}

Two casts ship with Magic:

  • EnumCast<T extends Enum>(values, {strict = false}): resolves a raw name to its enum value. Set strict: true to throw ArgumentError on unknown names instead of returning null.
  • ListCast<T>(inner): applies inner to every list element. Stored as a JSON-encoded string.

Roll your own by implementing CastsAttributes<T>:

dart
class MoneyCast implements CastsAttributes<Money> {
  const MoneyCast();

  @override
  Money? get(Model model, String key, Object? raw) =>
      raw == null ? null : Money.fromCents(raw as int);

  @override
  Object? set(Model model, String key, Object? value) =>
      value is Money ? value.cents : value;
}

Date Casting#

Date attributes are automatically converted to Carbon instances:

dart
class Event extends Model {
  @override
  Map<String, String> get casts => const {
    '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
  int? get durationInMinutes {
    if (startsAt == null || endsAt == null) return null;
    return endsAt!.diffInMinutes(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#

dart
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 serialised/deserialised automatically:

dart
class Monitor extends Model {
  @override
  Map<String, String> get casts => const {
    'settings': 'json',
    'tags': 'json',
  };
  
  // Access as Map
  Map<String, dynamic> get settings =>
      (getAttribute('settings') as Map<String, dynamic>?) ?? {};
  
  // Access as List
  List<String> get tags =>
      (getAttribute('tags') as List?)?.cast<String>() ?? [];
  
  // Setters
  set settings(Map<String, dynamic> value) =>
      setAttribute('settings', value);
  
  set tags(List<String> value) =>
      setAttribute('tags', value);
}

Using JSON Attributes#

dart
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"
esc

Search index did not load.

No match for "".

Try a class like rounded-lg or a widget like WButton.