A model cast as an object, and the money type it holds

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.