syno/money-bundle
Syno Money Bundle provides Symfony integration for working with monetary values and currencies, packaging money-related services and configuration to help keep amount handling consistent across your application.
Install (the bundle does not ship moneyphp/money transitively in a way you should rely on — require both, explicitly pinned):
composer require syno/money-bundle:^1.0 moneyphp/money:^4.0
Register the bundle. In a modern Symfony app this is a one-liner in config/bundles.php:
Tbbc\MoneyBundle\TbbcMoneyBundle::class => ['all' => true],
Minimum viable config in config/packages/tbbc_money.yaml:
tbbc_money:
reference_currency: EUR
currencies: [EUR, USD, GBP]
decimal_digits: 2
Everything else (storage, ratio provider) has defaults that work for a first smoke test.
Verify the wiring before writing code. The bundle is a thin DI layer, so the container is the API:
bin/console config:dump-reference tbbc_money
bin/console debug:container tbbc_money.money_manager
bin/console debug:twig --filter=money
bin/console debug:validator | grep -i money
Where to look first, in order
| File | Why |
|---|---|
DependencyInjection/Configuration.php |
The authoritative config tree. This is the only doc you can trust for a 0-star package. |
MoneyManager.php |
The façade service you'll inject into controllers/services. |
Form/Type/MoneyType.php + Resources/views/Form/ |
The composite amount+currency widget and its theme. |
Twig/Extension/* |
Exact filter names — they vary by fork, so don't guess from blog posts. |
Validator/Constraints/Money* |
The constraint and its validator. |
First use case — a product price. Add a price field to your entity, expose it with the bundle's form type, render it with the Twig filter, and guard it with the Money constraint. That loop (form → entity → template → validator) is 90% of what this bundle is for.
Route all money creation through the manager. Don't new Money(...) in controllers; inject tbbc_money.money_manager and call createMoney($amount, $currencyCode). The decimal_digits config decides how the amount argument is interpreted (decimal string vs. minor units) — read MoneyManager::createMoney() once, write a test that asserts createMoney(100, 'EUR') gives the value you expect, and then never think about it again.
Form usage. The bundle's MoneyType renders an amount input plus a currency select limited to your configured currencies. Two patterns worth adopting:
$builder->add('price', MoneyType::class, [
'currency' => 'EUR', // pin it when the user shouldn't choose
'scale' => 2,
]);
If your domain has exactly one currency per entity (most do), pin currency and skip the select entirely — it removes a whole class of validation bugs.
Persisting. Never store a float. Either store amount (integer minor units) + currency (3-char code) as two columns, or map Money as a Doctrine embeddable with those two properties and a __toString/getter that reconstitutes Money on hydrate. A DBAL custom type also works but is overkill for two scalar columns.
Currency conversion. Two providers:
ratio_provider: config — static rates in YAML. Correct for fixtures and tests, wrong for production FX.ratio_provider: doctrine — the bundle's CurrencyPair/Ratio entities, which you own the migrations and admin CRUD for.Either way, conversion happens through the manager. Wrap it: conversion hits the DB (or the config array) per call, so cache rates per request and never convert inside a loop over 200 order lines.
Serialization / API boundary. Emit money as strings, never JSON numbers:
{ "amount": "19.99", "currency": "EUR" }
Derive the string from Money::getAmount() / Money::getCurrency()->getSubUnit() at the serialization edge, so internal storage stays integer.
Validation. Combine the bundle's Money constraint (type/format) with your own (non-negative, currency whitelist, max for the field). The bundle's constraint won't know your business rules.
Testing. Use KernelTestCase and boot a real container; the services are private by default on Symfony 4+. Build expected values with moneyphp/money directly rather than with MoneyManager, otherwise you're asserting the object under test against itself.
tbbc/money-bundle. There is no forward-compat guarantee with Symfony 6.4/7.x or PHP 8.3+. Pin the exact version in composer.json, and budget for vendoring: the useful surface (form type, Twig filter, validator) is a few hundred lines.symfony/* constraints before installing. A 2022-era bundle may declare ^5.0 and still install under 6.x with a warning, then break on form theme paths or autowired constructor arguments. Run the test suite of the bundle's demo if one exists.decimal_digits is a single global value, but currencies don't share one. JPY has 0 minor digits, KWD has 3. A config of 2 silently rounds a JPY amount. For anything genuinely multi-currency, use Currency::getSubUnit() from moneyphp and treat the bundle setting as a display default only.reference_currency defaults to EUR. If you never set it, every conversion pivots through EUR in ways that are invisible until your rates are wrong. Set it explicitly even when you think it doesn't matter.intl. For user-facing prices, prefer NumberFormatter::CURRENCY and use the bundle filter only in admin/debug views, or you'll fight thousands separators per locale.Money instance (or null) on the model side. Passing a string from a fixture or a JSON payload throws deep inside the transformer with an unhelpful message. Validate the input type at your DTO boundary before it reaches the form.tbbc_money.money_manager via an alias in services.yaml or rely on autowiring by MoneyManager type; $container->get() in a controller will fail on modern Symfony.config provider. Same for the Twig extension if you need a house format. Forking the bundle for these is a waste; decorating is not.moneyphp/money directly, this bundle buys you a form type, a Twig filter, and a validator binding. Weigh that against an unmaintained dependency in your composer.json — for most applications, a small custom MoneyType and one Doctrine embeddable are cheaper and safer than adopting a zero-star 2022 fork.How can I help you explore Laravel packages today?