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

moneyphp/money-bundle

Symfony bundle integrating the Money PHP library, providing service configuration, a Twig extension, form type, and validator for handling monetary values and currencies safely across your application.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: moneyphp/money-bundle is a Symfony bundle, not a Laravel package. It is a small dependency-injection wrapper around moneyphp/money that registers Money\Currency, a zero-amount Money\Money, and (as of v0.1.0) an ISO Money\Currencies service from a default_currency config value. There is nothing in it that a Laravel application can consume — no service provider, no facade, no config publish, and symfony/framework-bundle is not in your dependency tree.

So for a Laravel codebase the practical starting point is:

composer require moneyphp/money

That library is the actual value: an immutable Money value object, ISO currency handling, parsers, formatters, allocation, and conversion. The bundle only matters if you are on Symfony and want those three services autowired.

If you are in a Symfony app, the bundle's full setup is:

composer require moneyphp/money-bundle
# config/packages/money.yaml
money:
    default_currency: EUR
// config/bundles.php
MoneyBundle\MoneyBundle::class => ['all' => true],

Then Money\Currency, a zero-amount Money\Money, and Money\Currencies (backed by ISOCurrencies) can be autowired. Before building anything on top, open vendor/moneyphp/money-bundle/src/Resources/config/services.xml and DependencyInjection/MoneyExtension.php — it takes two minutes and tells you the entire surface area of the package. As of v0.1.0 that is three service definitions and one config key.

First use case (Laravel): model an order total as an integer minor-unit column plus a currency column, and hydrate a Money object at the boundary.

use Money\Money;
use Money\Currency;

$price = new Money(1999, new Currency('USD')); // $19.99
$price->getAmount();              // 1999
$price->getCurrency()->getCode(); // "USD"

Implementation Patterns

1. Store minor units, never decimals. BIGINT for the amount, CHAR(3) for the currency, and pair them on the same table. Never store a DECIMAL and convert back and forth — that is where rounding bugs live.

Schema::create('orders', function (Blueprint $table) {
    $table->bigInteger('total_amount');
    $table->char('total_currency', 3);
});

2. Use an Eloquent cast to keep Money out of your attributes array.

class MoneyCast implements CastsAttributes
{
    public function get($model, string $key, $value, array $attributes): ?Money
    {
        if ($value === null) {
            return null;
        }

        return new Money(
            (int) $value,
            new Currency($attributes[$key.'_currency'] ?? config('money.default_currency'))
        );
    }

    public function set($model, string $key, $value, array $attributes): array
    {
        if (! $value instanceof Money) {
            throw new InvalidArgumentException("{$key} must be a Money instance.");
        }

        return [
            $key => $value->getAmount(),
            $key.'_currency' => $value->getCurrency()->getCode(),
        ];
    }
}

3. Bind the infrastructure singletons in a service provider. Currencies, formatters, and parsers are stateless and cheap to reuse. This is the Laravel equivalent of what the bundle now does in services.xml — one singleton() call each, no bundle required:

$this->app->singleton(Currencies::class, fn () => new ISOCurrencies());
$this->app->singleton(DecimalMoneyFormatter::class);   // "19.99"
$this->app->singleton(IntlMoneyFormatter::class, fn ($app) => new IntlMoneyFormatter(
    new \NumberFormatter('en_US', \NumberFormatter::CURRENCY),
    $app->make(Currencies::class)
));

Use DecimalMoneyFormatter in API resources (machine output) and IntlMoneyFormatter in Blade (human output).

4. Parse user input, don't cast it. Form input arrives as "19.99" strings; (int) "19.99" is 19.

$money = (new DecimalMoneyParser($currencies))->parse($request->input('amount'), $request->input('currency'));

5. Allocate for splitting, not division.

Money::USD(100)->allocate([1, 1, 1]); // 34, 33, 33 — no cents lost

6. Conversion goes through Money\Converter with an exchange-rate provider (a fixed map for tests, your own adapter for production rates). Do not multiply getAmount() by a float yourself.

7. In Symfony, autowire Money\Currencies and Money\Currency freely, but inject a factory/service for real amounts — see the gotcha below about the autowired Money\Money. If you need non-ISO currencies (crypto, loyalty points), decorate or replace the newly-registered Currencies service rather than rebuilding it at each call site.

Gotchas and Tips

  • Treat the bundle as pre-1.0 and unstable. v0.1.0 is explicitly the first release, so under semver's 0.x rules any minor bump may break you. Pin it (^0.1), and don't hard-code service IDs in compiled container extensions — read the XML in Resources/config/ instead, because the service names and definitions have already changed once between the initial shape and this release.
  • The autowired Money\Money service is amount 0. It exists so you can inject a Money, not your money. If you __construct(Money $money) and expect a real value, you get zero and a very quiet bug. Inject Money\Currency or build Money from your own data.
  • The new Currencies service is ISO-only. v0.1.0 adds a service definition of ISOCurrencies; it will happily reject or mis-scale a BTC/points currency. If you already bind your own Money\Currencies in a Laravel container, the bundle's addition changes nothing for you — but in Symfony, check which definition wins when you also register your own.
  • Equality is not ==. Two Money instances with the same amount but distinct Currency objects can surprise you. Always use $a->equals($b). Comparisons (greaterThan, lessThan) also assert matching currencies and throw otherwise.
  • Currency scale is not always 2. JPY and KRW have 0 decimals, KWD and BHD have 3. ISOCurrencies knows this; hardcoded /100 math does not. Use DecimalMoneyParser/DecimalMoneyFormatter so scale is derived from the currency.
  • Never create Money from a float. new Money(19.99, $currency) passes a float into an int parameter, which is silently truncated under non-strict types. Type-hint int and cast at the DB boundary only.
  • BIGINT on 32-bit PHP. Eloquent will hand back a string for values exceeding PHP_INT_MAX; the (int) cast in the snippet above will overflow silently. On 64-bit this is a non-issue for realistic amounts, but keep the cast explicit and validate upper bounds on input.
  • Money is immutable — there are no setters. Every operation returns a new instance. Forgetting to reassign ($money->add($fee); with no assignment) is the single most common bug with this library.
  • Rounding is opinionated, not configurable. ratio(), allocate(), and average() each define their own remainder handling and there is no global "round half even" switch. If finance signs off on a specific rounding mode, test it explicitly rather than assuming — and consider brick/money if you need configurable rounding everywhere.
  • Don't let Money leak into json_encode. Serialize explicitly in API resources (['amount' => $m->getAmount(), 'currency' => $m->getCurrency()->getCode()]) so your wire format is a deliberate contract rather than whatever the object happens to expose. Same for __toString() — don't rely on it.
  • Debugging tip: when a total looks wrong, dump both halves — dump($money->getAmount(), $money->getCurrency()->getCode()). Mismatches are almost always a currency mismatch or a scale assumption, not arithmetic.
  • Extension points (Symfony): override money.currency in services.yaml to change the default, and override the Currencies service to add non-ISO currencies. In Laravel the equivalent is simply binding your own Currencies implementation in the container — one line, no bundle required.
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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
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