kucharovic/money-bundle
A Symfony bundle that integrates the moneyphp/money library, providing Doctrine DBAL types, form types, validators, and Twig helpers for working with monetary values safely and consistently.
Install the bundle plus the underlying library. The bundle depends on moneyphp/money, but pin it explicitly so you know which Money API you're coding against. v1.3.0 is the first release with Symfony 5.x support, so constrain to it:
composer require kucharovic/money-bundle:^1.3 moneyphp/money
Register the bundle (Symfony <5.3 style bundles.php; on Symfony Flex it may not auto-register since the bundle predates the recipe era):
// config/bundles.php
return [
// ...
Kucharovic\MoneyBundle\MoneyBundle::class => ['all' => true],
];
Look here first. Open vendor/kucharovic/money-bundle/ and read, in this order:
MoneyBundle.php — what it actually registers.DependencyInjection/Configuration.php + Extension.php — the config tree, service IDs, and default currency keys.Doctrine/DBAL/Types/ and Form/Type/ — the exact FQCNs you'll import. The bundle is small enough that the source is the documentation; there is no full docs site.First use case: persist and format an amount. Add a Doctrine column typed as the bundle's money type, bind it to a form, and render it in Twig. That single vertical slice validates DBAL type registration, the form transformer, and the Twig filter all at once — the three things this bundle exists to provide.
Doctrine persistence. Register the DBAL type (or confirm the bundle did it) and map the field:
# config/packages/doctrine.yaml
doctrine:
dbal:
types:
money: Kucharovic\MoneyBundle\Doctrine\DBAL\Types\MoneyType
currency: Kucharovic\MoneyBundle\Doctrine\DBAL\Types\CurrencyType
use Money\Money;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Column(type: 'money')]
private Money $price;
public function changePrice(Money $price): void
{
// Money is immutable — always assign a new instance
$this->price = $price;
}
Keep currency in its own column (or embed it) rather than re-deriving it; Money::add() throws when currencies differ, so a mismatched pair in one row fails only at runtime.
Forms. Use the bundle's form type (alias the import — see gotchas) and give it a currency:
use Kucharovic\MoneyBundle\Form\Type\MoneyType as MoneyFormType;
$builder->add('price', MoneyFormType::class, [
'currency' => 'EUR',
// scale/precision options if the bundle exposes them
]);
This is where user input becomes a Money value object, so centralize parsing (decimal separators, thousands separators) here rather than sprinkling Money::of() calls through controllers.
Twig rendering. Use the bundle's filter/function for display and keep raw Money out of templates:
{{ order.total|money }} {# bundle filter #}
{{ order.total|money_format('EUR', 'de_DE') }} {# if a locale-aware variant exists #}
If the filter only does currency-symbol substitution, wrap NumberFormatter in your own Twig extension for locale-correct output — don't try to bend the bundle's filter into an i18n formatter.
Service layer. Inject Money/Currency as value objects in domain services; the bundle gives you the persistence and presentation edges, not a factory abstraction. For repeated construction from config, define a small MoneyFactory service that reads the bundle's default currency.
Testing. DBAL types are global state. In a base KernelTestCase/TestCase:
if (!Type::hasType('money')) {
Type::addType('money', \Kucharovic\MoneyBundle\Doctrine\DBAL\Types\MoneyType::class);
}
Also write one round-trip test (persist → refresh → assert equals()) that locks down the on-disk representation. That representation is effectively your schema contract, and it's the thing most likely to drift if you later swap the type.
src/ under your own namespace rather than patch around the bundle — the surface area is tiny.bundles.php, Twig extension): there's no service provider or config-publishing hook. On a Laravel project you can still install moneyphp/money and reuse the ideas here — a custom Eloquent cast for the money column, a Money form request/validator, a Blade directive — but none of the bundle classes will be auto-wired for you.moneyphp/money when you upgrade. Bumping to ^1.3 may resolve a newer Money major as a transitive dependency. Do the lockfile diff and check it before merging; the value-object API you code against is not owned by this bundle.Type::addType() is static/global and deprecated in places; prefer the doctrine.dbal.types config above. Registering the same type twice in a test suite throws — always guard with Type::hasType().MoneyType exists in both ...\Doctrine\DBAL\Types and ...\Form\Type, and Symfony ships its own MoneyType/CurrencyType form types. Alias every import or you'll silently wire the wrong one and get confusing transformer errors.1000) or a decimal string ("10.00") determines how you can query and index. Verify by inspecting a persisted row before writing any raw SQL or reporting queries against that column.10,00 and corrupt the column. Keep the form parser and the DBAL type in agreement, and never let a float touch the amount.Money is integer-minor-units by design; any (float) cast in a calculation or an average() in SQL bypasses that guarantee. Do totals in PHP with Money::add()/sum() or accept the precision loss knowingly.bin/console debug:container Kucharovic (or --tag=form.type) confirms the form types are registered; bin/console debug:config money (adjust to the actual alias) shows which defaults were applied; doctrine:schema:validate catches a missing type mapping.DataTransformer for the form type, and a MoneyParser chain for user-entered input. Prefer these over forking the bundle.How can I help you explore Laravel packages today?