padam87/money-bundle
A Symfony bundle that integrates moneyphp/money, providing Doctrine mapping types, form types, and Twig helpers for storing, formatting, and working with monetary values and currencies safely in your application.
Install the bundle and the underlying money library (it is pulled in transitively, but pinning helps):
composer require padam87/money-bundle brick/money
Register it (Symfony Flex does not auto-register this bundle — it is not a Flex recipe):
// config/bundles.php
return [
// ...
Padam87\MoneyBundle\Padam87MoneyBundle::class => ['all' => true],
];
Where to look first, in order:
README.md — the only real usage docs. Read it fully; it is short, and the v0.6.0 rewrite to brick/money changes the primary API surface.DependencyInjection/Configuration.php — the authoritative list of config keys (default_currency, decimal/precision options, etc.).Resources/config/ (or config/) — service IDs and tags. This is where you discover the Doctrine type name (usually money), the form type alias, and the Twig extension.Form/, Doctrine/ (or DBAL/Types/), Twig/ — the three integration surfaces you will actually use day-to-day.Minimal config:
# config/packages/padam87_money.yaml
padam87_money:
currency: EUR # verify the exact key in Configuration.php
# precision/scale if exposed
First use case — persist a price and render it:
use Brick\Money\Money;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Product
{
#[ORM\Column(type: 'money')] // bundle-provided DBAL type
private Money $price;
}
{{ product.price|money }} {# uses bundle Twig extension; check filter name #}
If the filter/form-type/DBAL type names differ in your installed version, bin/console debug:container --tag=twig.extension and bin/console debug:container --tag=form.type will list exactly what was registered.
Doctrine type as the primary integration point. Treat the bundle mainly as a DBAL type provider. Persist Brick\Money\Money directly on the entity and let the type convert to/from the database. Unlike moneyphp, brick/money Money embeds its currency intrinsically — verify how the bundled type serialises the currency: it may store both amount and currency in a single column (e.g. "10.00 EUR"), or expect a separate currency column. Read convertToDatabaseValue/convertToPHPValue before assuming either layout.
Multi-currency. Because Money now carries its currency, the natural pattern is one column per price:
#[ORM\Column(type: 'money')]
private Money $price;
…but if the bundled type only persists the numeric amount, fall back to storing currency separately and reconstruct one of:
Money::of('10.00', 'EUR'); // major units (decimal string), DefaultContext
Money::ofMinor(1000, 'EUR'); // minor units (int), same result
Money::of('10.00', 'EUR', new CustomContext(scale: 2)); // explicit scale/context
Forms. Use the bundle's money form type (compound amount + currency) or wrap with a DataTransformer so the form still binds a single Money object:
$builder->add('price', MoneyType::class, [
'currency' => 'EUR', // important: do not rely on the global default in forms
'divisor' => 100, // if supported — converts user input to minor units
]);
Twig formatting. Prefer the bundle extension over hand-rolled number_format. Note that brick/money does not ship a formatter (unlike moneyphp's MoneyFormatter), so any formatting service the bundle exposes is either a thin wrapper around NumberFormatter or its own implementation — check Resources/config/ to confirm the service ID and whether it honours the currency's subunit (JPY = 0, BHD = 3) before relying on it.
Service-level usage. Inject Brick\Money\Money factories or the bundle's money formatter directly into services. Keep arithmetic in Money objects and convert to a display string only at the edge:
$total = Money::total(...$lineItems); // sum a list of Monies (was Money::sum)
$withTax = $total->multipliedBy('1.20'); // string/BigNumber multiplier, not float
$formatted = $this->moneyFormatter->format($total); // bundle-wired formatter service
Arithmetic operations (plus, minus, multipliedBy, dividedBy, allocatedTo) return new instances and enforce context compatibility (see Gotchas).
Serialization. Brick\Money\Money implements JsonSerializable and emits ['amount' => '10.00', 'currency' => 'USD'] — a decimal string in major units, not minor units. Either accept that shape, or add a normalizer that converts to minor units for ledger-style APIs; either way, document the unit and precision in your OpenAPI spec.
Testing. Build fixtures with Money::of('10.00', 'EUR') or Money::ofMinor(1000, 'EUR') and assert against formatted strings. Use Money::total(...) in expectations rather than computing with intval — the BigNumber scale and context on both sides must match.
moneyphp/money → brick/money. Any existing code that imports Money\Money, Money\Currency, or calls Money::sum(), Money::EUR() (moneyphp's signature), or uses MoneyFormatter will break. Migrate imports to Brick\Money\Money / Brick\Money\Currency, and swap Money::sum(...) for Money::total(...). Pin to ^0.6 and read every PR between 0.5.1 and 0.6.0 before touching a production branch.Money::ofMinor(1000, 'USD') is $10.00; Money::of('10.00', 'USD') is also $10.00; Money::of('1000', 'USD') is $1,000.00. This is the #1 source of bugs in brick/money because there is no single "amount" constructor. Verify what the bundle's form type produces and what the DBAL type writes before trusting either.plus()/minus() throw MoneyMismatchException if the two operands use different contexts (DefaultContext vs CashContext vs CustomContext). All Monies in one column/field must be constructed with the same context — set that at the DBAL type/hydrator, not scattered across services.getAmount() returns a BigNumber, not an int. $money->getAmount() gives you a decimal string (e.g. '10.00'). Use (string) or getAmount()->toBigDecimal() — do not hand it to functions expecting an integer. getMinorAmount() / getUnscaledAmount() return BigNumber too, so cast them for integer storage.Money now. Check whether the bundled DBAL type persists it (single combined column) or requires a companion currency column. A Money cannot be hydrated from amount alone, so a type that writes only the numeric amount will silently lose currency on read.DECIMAL without understanding the type. The DBAL type should write a DECIMAL/VARCHAR (brick/money's default) or an integer (minor units, with an explicit context). If you see 10.0 where you expected 1000, check whether the writer used major or minor units.Money is not nullable. Either make the property ?Money with a nullable-aware type wrapper, or guard in setters. A NULL column will throw on hydration.composer.json and read the changelog before upgrading; API surface is not battle-tested across Symfony majors.Locale in the request stack — pass one explicitly or your output will fall back and be wrong for non-en currencies. This matters more now that brick/money has no shipped formatter to fall back on.bin/console debug:config padam87_money — confirm your config is picked up (and the key names are right).bin/console debug:container --tag=doctrine.dbal.types — confirm the money type is registered and points at the Brick\Money implementation, not the legacy moneyphp one.bin/console doctrine:schema:validate — catches type/column mismatches early.bin/console debug:twig — confirm the extension and filter names.{% block money_widget %}) for custom UI; register your own DBAL type (backed by brick/money) if you need multi-currency or a specific context (e.g. CashContext(5) for CHF) instead of fighting the bundled one.Padam87\MoneyBundle is not auto-registered. A fresh clone on a new machine will produce "class not found" for the type if you forgot config/bundles.php. This bites CI setups that don't run Flex recipes — and doubly so after v0.6.0, since any stale moneyphp/money still in the lockfile will mask the breakage until a clean install.How can I help you explore Laravel packages today?