elegantly/laravel-money
Laravel integration for brick/money: Eloquent casting to Brick\Money\Money, safe parsing from strings/ints/floats, and a ValidMoney validation rule with min/max bounds and nullability. Store amounts cleanly (with currency column or fixed currency).
composer require elegantly/laravel-money
default_currency) with:
php artisan vendor:publish --tag="money-config"
Money in your Invoice model:
use Elegantly\Money\MoneyCast;
protected $casts = [
'amount' => MoneyCast::of('currency'), // Dynamic currency
// OR
'price' => MoneyCast::of('USD'), // Fixed currency
];
bigInteger for amounts and string for currency (ISO 4217):
Schema::create('invoices', function (Blueprint $table) {
$table->bigInteger('amount'); // e.g., 1000 = $10.00
$table->string('currency', 3); // 'USD', 'EUR'
});
Dynamic Currency: Reference a currency column for flexible multi-currency models:
class Invoice extends Model {
protected $casts = [
'amount' => MoneyCast::of('currency'), // Uses `currency` column
];
}
Use case: E-commerce platforms with variable currency support.
Fixed Currency: Hardcode currency for models with static currencies:
class Subscription extends Model {
protected $casts = [
'price' => MoneyCast::of('EUR'), // Always EUR
];
}
Use case: SaaS subscriptions tied to a single currency.
Convert strings/ints/floats to Money safely:
use Elegantly\Money\MoneyParser;
// API/Request handling
$amount = MoneyParser::parse($request->input('price'), 'USD');
// Form validation (Livewire/Form Requests)
$validated = $request->validate([
'price' => ['required', new ValidMoney(min: 0, max: 1000)],
]);
Tip: Use MoneyParser in setters or accessors to sanitize input early.
public function rules() {
return [
'price' => [new ValidMoney(nullable: false, min: 0, currency: 'USD')],
];
}
#[Validate([new ValidMoney(min: 0, max: 100)])]
public ?int $price = null;
Pattern: Centralize validation logic in a base request class or trait.
Brick\Money methods in queries:
$invoices = Invoice::where('amount', '>=', Money::of(100, 'USD'))
->get();
Money arithmetic:
$total = Invoice::sum('amount'); // Returns Money object
Mock Money objects in tests:
public function test_invoice_total() {
$invoice = new Invoice(['amount' => Money::of(100, 'USD')]);
$this->assertEquals('100.00', $invoice->amount->getAmount()->toString());
}
Tip: Use Money::of() for test data to ensure consistency.
Floating-Point Serialization:
MoneyCast serializes to float (e.g., 100.00) instead of formatted strings.serialize in your model if you need formatted output:
public function getAmountAttribute($value) {
return $value->getAmount()->toString();
}
Currency Column Mismatch:
MoneyCast throws an exception.config/money.php or handle missing columns:
$casts = [
'amount' => MoneyCast::of('currency', default: 'USD'),
];
Validation Edge Cases:
ValidMoney may reject valid inputs if min/max are set as floats (e.g., 0.99).new ValidMoney(min: '0.99', max: '100.00')
Database Schema Mismatch:
decimal instead of bigInteger for amounts can introduce precision errors.bigInteger (e.g., 10000 = $100.00) and let Money handle formatting.MoneyParser::parse() directly to debug input:
dd(MoneyParser::parse($userInput, 'USD'));
dd($model->getCasts());
toArray() for debugging:
public function toArray() {
return array_merge(parent::toArray(), [
'amount_debug' => $this->amount->getAmount()->toString(),
]);
}
Custom Rounding:
MoneyParser rounding mode (v2.3.2+):
config(['money.rounding_mode' => \Brick\Math\RoundingMode::UP]);
Additional Validation:
ValidMoney for custom rules (e.g., tax compliance):
class ValidTaxAmount extends ValidMoney {
public function passes($attribute, $value) {
return parent::passes($attribute, $value) &&
$value->getAmount() >= Money::of(10, 'USD');
}
}
Currency Conversion:
brick/math or a third-party API:
use Brick\Money\Money;
$usd = Money::of(100, 'USD');
$eur = $usd->convertTo('EUR'); // Requires exchange rate logic
Money behavior (e.g., skip created_at).Money arithmetic in queries sparingly—fetch data first for complex calculations:
// Slow: Avoid in loops
Invoice::where('amount', '>=', Money::of(100, 'USD'))->get();
// Fast: Fetch first, then filter
$invoices = Invoice::all();
$filtered = $invoices->filter(fn($i) => $i->amount >= Money::of(100, 'USD'));
float/decimal columns to bigInteger:
// Migration
Schema::table('invoices', function (Blueprint $table) {
$table->bigInteger('amount')->default(0);
});
// Update data
Invoice::query()->update([
'amount' => Invoice::selectRaw('ROUND(amount * 100)')->value('amount'),
]);
How can I help you explore Laravel packages today?