headsnet/money-bundle
A Symfony bundle that integrates the moneyphp/money library, adding Doctrine types, form types, validators, Twig extensions and serialization support so your app can handle monetary values safely and consistently.
composer require headsnet/money-bundle. As of v0.2.0 the minimum PHP version is 8.1 — if you are still on PHP 8.0 you must pin ^0.1.12.moneyphp/money. In Laravel you will not register the bundle itself; instead pull in moneyphp/money (a transitive dependency) and wire the two useful services yourself: MoneyFactory (create Money from user input/strings) and MoneyFormatter (render Money for display).src/ for the factory/formatter classes, and vendor/moneyphp/money/src/Money.php for the arithmetic API you will actually call day-to-day.Money object and formatting it for an invoice or price display:$money = MoneyFactory::create(1999, 'GBP'); // or from '19.99'
echo MoneyFormatter::format($money); // "£19.99"
ext-intl enabled (php -m | grep intl) — the formatter relies on it.$this->app->singleton(MoneyFormatter::class, fn ($app) => new MoneyFormatter(
currency: config('money.default_currency', 'GBP'),
locale: config('app.locale', 'en_GB'),
));
class MoneyCast implements CastsAttributes
{
public function get($model, $key, $value, $attributes): ?Money
{
return $value === null ? null : Money::{$attributes['currency'] ?? 'GBP'} ((int) $value);
}
public function set($model, $key, $value, $attributes): array
{
return [$key => $value instanceof Money ? $value->getAmount() : (int) $value];
}
}
'19.99') and parse with the factory rather than doing float math. Never let a float reach Money — it will throw or silently mis-scale.$price->multiply($qty, RoundingMode::HALF_UP)->subtract($discount).$total->allocate([1, 1, 1]) guarantees the parts sum back to the original, unlike naive division.equals(), greaterThan(), lessThan() — do not compare getAmount() integers across different currencies, it will throw InvalidArgumentException.Money is not JSON-serializable by default. Add a jsonSerialize() wrapper or an API Resource that emits ['amount' => ..., 'currency' => ...] explicitly.@money($invoice->total) registered via Blade::directive('money', ...).app.locale per request — locale is stateful inside intl.8.1, 8.2, 8.3, 8.4) and any composer.json platform constraint in one go — otherwise composer update will silently hold you on 0.1.x.Headsnet\MoneyBundle\...Bundle in config/app.php; extend Illuminate\Support\ServiceProvider and instantiate the services directly.string $x = null) are deprecated in 8.4. If you have subclassed or wrapped formatter/factory signatures, make nullability explicit (?string $x = null) before upgrading.100 as a divisor — use $money->getCurrency()->getSubunit().multiply()/divide(). Omitting it (or defaulting to UNNECESSARY) throws on non-exact results, which surfaces randomly in tax calculations — always pass RoundingMode::HALF_UP or HALF_EVEN deliberately.getAmount() instead; CI images with different ext-intl builds will produce different non-breaking-space characters.Money constructor throws, the message names the currency but not the caller — wrap creation in your factory with a try/catch that logs the raw input value; malformed user input is the usual culprit.MoneyFactory is a static-ish convenience. If you need multi-currency conversion or custom currencies, inject a Currencies instance from moneyphp/money and construct Money yourself — that is the seam to build on.How can I help you explore Laravel packages today?