Two integer columns and a currency string, reassembled by hand in every place that needed a total.
final class AsMoney implements CastsAttributes
{
public function get($model, string $key, $value, array $attrs): Money
{
return new Money($attrs['total_cents'], $attrs['currency']);
}
public function set($model, string $key, $value, array $attrs): array
{
return ['total_cents' => $value->cents, 'currency' => $value->currency];
}
}
protected $casts = ['total' => AsMoney::class];
A cast that reads several columns and writes several is the useful shape and it is the one the documentation covers least. The trap is querying: where('total', $money) does not work, because the cast applies to attribute access and not to the query builder, so the query layer still deals in cents. Making that explicit with a scope was clearer than pretending the abstraction was complete.