yceruto/money-bundle
A Symfony bundle that integrates MoneyPHP into your app, offering form types, Twig extensions, Doctrine mapping, validators, and serializers for handling monetary amounts and currencies safely and consistently.
Install the bundle with Composer and let Flex wire it up:
composer require yceruto/money-bundle
If you are not using Symfony Flex (or the recipe is not available for your version), register it manually in config/bundles.php:
return [
// ...
Yceruto\MoneyBundle\MoneyBundle::class => ['all' => true],
];
The bundle is a thin integration layer over moneyphp/money, so the first thing to read is the bundle's own DependencyInjection/Configuration.php and Resources/config/*.yaml in vendor/yceruto/money-bundle/. That tells you exactly which services, Doctrine types, form types, and Twig helpers are registered and what config keys exist. Do this before reading the README — the config tree is the source of truth for a small bundle like this.
A minimal config/packages/money.yaml is usually enough:
money:
default_currency: EUR
Your first end-to-end use case should be "store a price on an entity and render it":
Money property to a Doctrine entity and map it with the bundle's DBAL type (check the type name registered by the bundle — typically money).MoneyType field so users enter amount + currency.If any of those three pieces is missing in your version, the underlying moneyphp/money API still works — the bundle just removes boilerplate.
Doctrine persistence. Prefer the bundle's DBAL type over a manual amount/currency pair so hydration always returns a Money object instead of a raw string. Two common storage layouts:
amount BIGINT (minor units) + currency CHAR(3).Whichever the bundle uses, align your migrations with it. When generating a migration, verify the generated ADD/ALTER statements use the right column type — DBAL custom types often need ->setColumnType() or a manual edit.
Form handling. Use the provided MoneyType for input rather than two bare text fields. If your users type "12,50 €" rather than 1250, parse it with Money\Parser\DecimalMoneyParser (or AggregateMoneyParser for multi-currency) in a form data transformer, and let the bundle's field handle the amount/currency split. Keep the transformation at the form boundary so your domain always receives a valid Money.
Serialization / APIs. Register the bundle's normalizer and expose money as an object:
{ "amount": "1250", "currency": "EUR" }
Document for your frontend team that amount is in minor units and is a string to avoid JS integer overflow. If you need a different shape ("{ "value": 12.50, "currency": "EUR" }"), write a small custom normalizer and tag it serializer.normalizer with a higher priority than the bundle's.
Service-layer usage. Inject Money factories or the bundle's configured currency/parser services into your pricing, cart, and invoice services instead of new Money(...) scattered through the code. That keeps currency defaults and rounding policy in one place.
Twig rendering. Use the bundle's money filter/function in templates, passing the locale explicitly where the app is multi-locale. For PDFs and emails, render server-side with the same helper so formatting stays consistent with the web UI.
Validation. Combine the form type with a GreaterThan(0) or custom constraint on the underlying amount. moneyphp/money throws InvalidArgumentException on malformed input, so wrap parsing in try/catch and convert to form errors rather than letting a 500 escape.
Money::EUR(1000) is €10.00, not €1000. Every bug report about "amounts are 100x off" traces back to this. Write the conversion in exactly one place (a parser/form transformer) and use it everywhere.Money from strings or integers. Money::EUR(0.1 + 0.2) will bite you; Money::EUR('30') will not.$money->add($other) returns a new instance. Forgetting to reassign ($total = $total->add($line)) silently loses data.InvalidArgumentException. Guard at boundaries (cart vs. catalog currency) with an explicit check and a domain exception.Unknown column type "money" after install, the bundle's DBALTypes registration did not run — check that the bundle is enabled and that doctrine.dbal.types is not overridden in your config. Clearing the cache (bin/console cache:clear) after config changes is usually required.doctrine:migrations:diff may emit VARCHAR or fail on a custom type. Set the column type explicitly in the entity mapping and review every generated migration by hand.default_currency and a couple of toggles. Run bin/console debug:config money and bin/console debug:container --tag=… to see what's actually wired before assuming a feature exists.amount in minor units, ISO code) is for machines, not users. Do not surface it directly in an API consumed by non-technical clients without a documented contract.config/services.yaml (same service id, decorates: or a compiler pass) for custom normalizers, Twig extensions, or a different parser. Prefer decoration over forking; the bundle is small and its service IDs are stable enough for this.Money objects ($this->assertEquals(Money::EUR(1250), $order->getTotal())), not on strings. In functional tests, seed the DB with minor-unit integers so fixtures match what the DBAL type hydrates.How can I help you explore Laravel packages today?