search ESC

Searching…

No results for "".

Type at least 2 characters to search.

Docs

Eloquent: Mutators

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.

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)

Custom Casts (CastsAttributes)

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

enum MonitorStatus { active, paused, failed }

class Monitor extends Model {
  @override
  Map get casts => {
    '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(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(inner) — applies inner to every list element. Stored as a JSON-encoded string.

Roll your own by implementing CastsAttributes:

class MoneyCast implements CastsAttributes {
  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:

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.