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

tbbc/money-bundle

A Symfony bundle that integrates the moneyphp/money library into your application, offering Twig filters and extensions for formatting monetary values, form types for money input, and ready-to-use services for safe currency handling.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the bundle and enable it in Symfony:

composer require tbbc/money-bundle
// config/bundles.php
return [
    // ...
    Tbbc\MoneyBundle\TbbcMoneyBundle::class => ['all' => true],
];

Configure the currencies and reference currency. The exact config keys are version-dependent, but the common setup is:

# config/packages/tbbc_money.yaml
tbbc_money:
    currencies: ['EUR', 'USD']
    reference_currency: 'EUR'
    exchange_rates:
        USD: 1.10

Clear the cache and inspect what the bundle registered:

php bin/console cache:clear
php bin/console debug:container tbbc_money

The first practical use case is a product price: accept a Money object in a form, persist the amount and currency separately, and format it in Twig.

use Tbbc\MoneyBundle\Form\Type\MoneyType;

$builder->add('price', MoneyType::class, [
    'currency' => 'USD',
]);
{{ product.price|money_format }}
{{ product.price|money_intl_format }}

Start by reading the bundle’s form type, Twig extension, and converter service definitions. Do not begin by coupling your entities directly to the bundle; treat it as an adapter around moneyphp/money.

Implementation Patterns

Keep moneyphp Money in the domain, not in Doctrine.

Use integer minor units plus an ISO currency code in persistence. Reconstruct Money in the entity or a mapper.

use Money\Currency;
use Money\Money;

final class Product
{
    private int $priceAmount;
    private string $priceCurrency;

    public function getPrice(): Money
    {
        return new Money($this->priceAmount, new Currency($this->priceCurrency));
    }

    public function setPrice(Money $price): void
    {
        $this->priceAmount = (int) $price->getAmount();
        $this->priceCurrency = $price->getCurrency()->getCode();
    }
}
#[ORM\Column(type: 'bigint')]
private int $priceAmount;

#[ORM\Column(length: 3)]
private string $priceCurrency;

Use the form type as the UI boundary.

For a fixed-currency field:

$builder->add('price', MoneyType::class, [
    'currency' => 'USD',
]);

For a user-selectable currency, keep amount and currency as separate fields and combine them after validation:

$builder
    ->add('amount', MoneyType::class, ['currency' => 'USD'])
    ->add('currency', CurrencyType::class);
$money = new Money(
    $form->get('amount')->getData()->getAmount(),
    $form->get('currency')->getData()
);

Format only at the presentation edge.

Keep raw Money objects in services and controllers. Format in Twig or an API presenter.

{{ order.total|money_format }}
{{ order.total|money_intl_format }}

If the bundled Twig filter signature differs in your version, inspect the extension service with debug:container and use the formatter service directly in PHP.

Inject the formatter or converter rather than duplicating formatting logic.

use Tbbc\MoneyBundle\Formatter\MoneyFormatterInterface;

final class InvoicePresenter
{
    public function __construct(private MoneyFormatterInterface $formatter)
    {
    }

    public function present(Money $money): string
    {
        return $this->formatter->formatMoney($money);
    }
}

Check debug:container for the actual service IDs and aliases. If interface autowiring is not configured, bind it explicitly:

services:
    Tbbc\MoneyBundle\Formatter\MoneyFormatterInterface:
        alias: tbbc_money.formatter.money_formatter

Use the bundle converter for display-rate conversions, not accounting.

use Money\Currency;
use Tbbc\MoneyBundle\Pair\PairConverterInterface;

final class PriceConverter
{
    public function __construct(private PairConverterInterface $converter)
    {
    }

    public function toEur(Money $usd): Money
    {
        return $this->converter->convert($usd, new Currency('EUR'));
    }
}

Record the rate and timestamp externally if the converted value is shown later. Do not treat a config exchange rate as a settlement rate.

Serialize money explicitly.

Prefer a stable API shape over exposing the Money object:

{
  "amount": 1299,
  "currency": "USD",
  "formatted": "$12.99"
}

or, if you must expose major units:

{
  "amount": "12.99",
  "currency": "USD"
}

Test with fixed rates and explicit locales.

Override the rate configuration in the test environment, format with a fixed locale, and assert integer minor units in assertions. Avoid tests that depend on live exchange providers or the host’s default locale.

Gotchas and Tips

  • Money amounts are integer minor units. Money::USD(1299) means $12.99; Money::USD(12.99) is wrong and may be coerced or rejected.
  • Currency scale is not always 2. JPY has no minor unit, KWD has three. Do not hardcode /100; use the formatter or the currency’s subunit.
  • Money is immutable. multiply(), add(), and subtract() return new instances.
  • Rounding matters during multiplication and conversion. Pass an explicit rounding mode when multiplying by quantities or percentages, and avoid chained conversions.
  • Form types generally expect a Money object. Empty or null input often needs explicit empty_data handling or a nullable wrapper; otherwise transformers can throw.
  • Localized decimal input can break if the user’s locale uses commas. Test the form with the same locale the application uses.
  • money_intl_format depends on the PHP intl extension and locale. Output can differ across environments and tests; pin the locale.
  • The static/config exchange-rate provider is convenient but stale by nature. If rates are stored via Doctrine, write migrations, enforce one rate per currency pair, store rates as strings/decimals, and timestamp updates.
  • Rate direction and triangulation are easy to get wrong. If rates are relative to a reference currency, converting USD to EUR may go through the reference currency; verify both directions.
  • Autowiring for bundle interfaces is not guaranteed across versions. Run debug:container and add aliases for MoneyFormatterInterface, PairConverterInterface, or rate-provider interfaces if needed.
  • CurrencyType choices typically come from configuration. Adding a currency requires a config change and cache clear.
  • Do not persist a serialized Money object. Persist amount and currency columns; map explicitly.
  • Do not rely on default JSON serialization of Money. Normalize to a documented API contract.
  • This is a niche bundle with a small ecosystem. Check Symfony version constraints before adopting it, read the source for the exact service IDs and config keys in your installed version, and prefer wrapping it behind your own formatter/converter interfaces so it is replaceable.
  • Use the bundle’s extension points when needed: implement a custom exchange-rate provider or storage adapter if the bundled config/Doctrine providers do not match your infrastructure.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core