Use a custom Eloquent “Castable” class (i.e. a Value Object) for your model attributes so you get strong typing, encapsulated logic and automatic DB conversion.
Create your Value Object implementing the Castable & CastsAttributes contracts:
use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class Money implements Castable, CastsAttributes
{
public int $amount;
public string $currency;
public function __construct(int $amount, string $currency = 'USD')
{
$this->amount = $amount;
$this->currency = $currency;
}
// Eloquent will call this when retrieving
public function get($model, string $key, $value, array $attributes)
{
[$amount, $currency] = explode(':', $value);
return new self((int)$amount, $currency);
}
// Eloquent will call this when saving
public function set($model, string $key, $value, array $attributes)
{
/** @var Money $value */
return "{$value->amount}:{$value->currency}";
}
// required by Castable
public static function castUsing(array $arguments)
{
return new self(...$arguments);
}
}
Register it in your model’s $casts:
class Product extends Model
{
protected $casts = [
'price' => Money::class.':USD', // optional constructor args
];
}
Now $product->price is a Money object with your domain logic, and Eloquent handles serialization to/from the database automatically.