Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Money Bundle Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install with 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.
  • First place to look: the 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.
  • First use case: store amounts as integer minor units (cents) in a 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.

Implementation Patterns

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:

  1. composer update garak/money-bundle
  2. Run the suite; grep for money assertions: grep -rn "assertSee\|assertEquals" tests/ | grep -E '\$|€|£'
  3. Replace literal expectations with "\u{00A0}" or normalise before comparing.
  4. Spot-check PDF/mail rendering (see Gotchas).

Gotchas and Tips

  • The NBSP is not a space. 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.
  • Font and PDF rendering. Dompdf/mPDF and some embedded fonts lack a U+00A0 glyph, producing tofu boxes or a fallback space in invoices. Verify one PDF per currency after upgrading; if it breaks, override the formatter to emit a plain space for that channel.
  • CSV/Excel exports. U+00A0 survives into spreadsheets and can defeat VLOOKUP/filters and copy-paste into numeric columns. Export raw integers for machine consumers, formatted strings only for human-facing output.
  • Laravel ≠ Symfony. The bundle's configuration is not read from 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 are currency-dependent. JPY has 0, BHD has 3. A single decimals: 2 default in your provider will silently mangle those. Derive from the currency's exponent, or key the formatter config by currency code.
  • Never feed floats to Money. Use (int) minor units or Money::stringToUnits('12.34'). Floats introduce off-by-one-cent bugs that only show up in totals.
  • Round before persisting. The formatter rounds for display only; do business rounding with an explicit RoundingMode before the amount hits the database, otherwise reports and invoices disagree.
  • Negative amounts. Accounting-style ($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.
  • Extension point. If you need a different output shape, extend or replace the formatter class and rebind it in the container (services.yaml under Symfony, $this->app->bind(...) under Laravel) rather than post-processing its string output — post-processing is where the NBSP gremlins live.
  • Version pinning. This is a small, narrowly-scoped bundle; pin ^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.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core