Converting a column to a value object in an accessor works until something writes to the model, at which point there are two representations and one of them is wrong.
final class AsMoney implements CastsAttributes
{
public function get($model, $key, $value, array $attributes): ?Money
{
return $value === null
? null
: new Money((int) $value, $attributes['currency']);
}
public function set($model, $key, $value, array $attributes): array
{
return [$key => $value->cents(), 'currency' => $value->currency()];
}
}
A cast covers both directions and the accessor covers one, which is the whole argument. Returning raw column values from set is what keeps the dirty check working — it compares the raw attributes, so returning an object marks the model dirty on every assignment even when nothing changed. Casting a column that feeds two columns, as here with the currency, is supported and is the case that makes casts worth the file.