garak/money-bundle
garak/money-bundle is a Symfony bundle that integrates moneyphp/money, adding Doctrine types, form fields, validators, and Twig helpers so you can handle monetary amounts and currencies consistently.
composer require garak/money-bundle. If you are on Laravel rather than Symfony, expect to do some manual wiring: the package ships as a Symfony bundle, so its DI extension (DependencyInjection/*Extension) and Resources/config/services.* are only auto-loaded by a Symfony kernel. Read those service definitions first — they tell you exactly which classes to bind in a Laravel service provider.MoneyFormatter (and its configuration tree) plus the small set of classes around it. That formatter, backed by moneyphp/money, is the whole value of the package — everything else is Symfony glue.bigint column plus a char(3) currency column, then format only at the presentation edge.// app/Providers/MoneyServiceProvider.php
public function register(): void
{
$this->app->singleton(MoneyFormatter::class, fn () => new MoneyFormatter(
decimals: 2,
decimalSeparator: '.',
thousandsSeparator: ',',
// check the package's config keys for the exact constructor/args
));
}
{{-- resources/views/invoices/show.blade.php --}}
@money($invoice->total_cents, $invoice->currency)
v1.8.0 note: the space between the amount and the currency symbol is now a non-breaking space (U+00A0). Any snapshot, assertSee, or JS test that expects "$1,000.00" with a plain 0x20 will start failing. Treat this as a formatting-only change — no API or data migration — but sweep your test suite for exact-string money assertions.
Keep formatting at the edge. Persist and transport minor units as integers plus an ISO currency code; format in Blade, mail templates, and PDFs. Never round-trip a formatted string back into storage.
Blade directive or helper. Rather than injecting the formatter into every view, register a directive and a helper once:
Blade::directive('money', fn ($expr) => "<?php echo e(app(\MoneyFormatter::class)->format($expr)); ?>");
function money(int $units, string $currency = 'USD'): string
{
return app(MoneyFormatter::class)->format(new Money($units, new Currency($currency)));
}
Custom Eloquent cast so domain code deals in Money while the schema stays boring:
class MoneyCast implements CastsAttributes
{
public function get($model, $key, $value, $attributes): ?Money
{
return $value === null ? null : new Money((int) $value, new Currency($attributes['currency']));
}
public function set($model, $key, $value, $attributes): array
{
return [$key => $value instanceof Money ? (int) $value->getAmount() : (int) $value];
}
}
Per-use-case formatters. Accounting exports, invoices, and dashboards rarely share separators/decimals. Bind named instances in the container (money.invoice, money.report) rather than mutating a global config, which is the classic source of "the PDF format changed and broke the dashboard" bug.
API responses. Return { "amount": 129900, "currency": "USD" } and format client-side (via Intl.NumberFormat) so you never ship locale-specific whitespace over the wire. If a formatted field is genuinely needed, document the NBSP explicitly.
Migration workflow for a bump like v1.8.0:
composer update garak/money-bundlegrep -rn "assertSee\|assertEquals" tests/ | grep -E '\$|€|£'"\u{00A0}" or normalise before comparing.trim() does not strip U+00A0, str_contains($s, ' ') fails, and explode(' ', ...) leaves it glued to the number. Normalise with preg_replace('/\x{00A0}/u', ' ', $string) before comparisons, or compare against "\u{00A0}" deliberately. Debug with bin2hex($formatted) when output "looks identical" but assertions fail.VLOOKUP/filters and copy-paste into numeric columns. Export raw integers for machine consumers, formatted strings only for human-facing output.config/money.php automatically. If you add such a file, plumb it into your provider by hand, and remember php artisan config:cache freezes those values — clear it after changing separators.decimals: 2 default in your provider will silently mangle those. Derive from the currency's exponent, or key the formatter config by currency code.Money. Use (int) minor units or Money::stringToUnits('12.34'). Floats introduce off-by-one-cent bugs that only show up in totals.RoundingMode before the amount hits the database, otherwise reports and invoices disagree.($1,000.00) vs -$1,000.00 is usually the first "why does this look wrong" ticket — check what the formatter does with negatives and wrap it if you need parentheses.services.yaml under Symfony, $this->app->bind(...) under Laravel) rather than post-processing its string output — post-processing is where the NBSP gremlins live.^1.8 and read the diff before each bump, since display-only changes are the norm and the changelog is the only signal you'll get.How can I help you explore Laravel packages today?