brick/money
Brick\Money is a PHP library for precise, immutable money and currency values. It provides exact arithmetic (no float errors), explicit rounding control, and supports large amounts via brick/math, with optional GMP/BCMath acceleration.
Installation:
composer require brick/money
Ensure PHP 8.2+ is used (or downgrade for older versions if needed).
First Use Case:
Create a Money object from a numeric value and currency:
use Brick\Money\Money;
$price = Money::of(19.99, 'USD'); // Immutable USD 19.99
Key Classes to Know:
Money: Core class for immutable monetary values.MoneyBag: For aggregating multiple currencies.RoundingMode: For handling precision (e.g., RoundingMode::Up).Context\*: Customize behavior (e.g., CashContext for cash rounding).Where to Look First:
Immutable Operations:
All methods return new Money instances. Avoid modifying state:
$total = $subtotal->plus($tax)->minus($discount);
Currency Safety: Validate currencies early to avoid runtime errors:
try {
$total = $usd->plus($eur); // Throws CurrencyMismatchException
} catch (CurrencyMismatchException $e) {
// Handle or log
}
Rounding Strategies:
Pass RoundingMode explicitly for operations requiring precision:
$rounded = $money->dividedBy(3, RoundingMode::HalfUp);
Context-Driven Logic: Use contexts for domain-specific rules (e.g., cash rounding for CHF):
$chf = Money::of(10, 'CHF', new CashContext(step: 5));
Laravel-Specific:
Money to the container for dependency injection:
$this->app->bind(Money::class, function () {
return Money::of(0, 'USD'); // Default zero-money
});
brick/money in Form Requests:
use Brick\Money\Money;
use Brick\Money\Exception\CurrencyMismatchException;
public function rules()
{
return [
'price' => ['required', function ($attribute, $value, $fail) {
try {
$money = Money::of($value, 'USD');
} catch (Exception $e) {
$fail('Invalid price format.');
}
}],
];
}
Database Storage: Store minor units (cents) in integers to avoid floating-point issues:
$money = Money::ofMinor(1234, 'USD'); // Store 1234 in DB
$retrieved = Money::ofMinor($storedValue, 'USD');
API Responses:
Serialize Money to JSON with currency:
$money->getAmount()->toString(); // "19.99"
$money->getCurrency()->getSymbol(); // "$"
Testing:
Use Money::ofMinor() for predictable test data:
$this->assertEquals(
Money::ofMinor(100, 'USD'),
Money::of(1, 'USD')->multipliedBy(100)
);
Rounding Mode Scope:
// Wrong: Assumes rounding persists
$money->plus('0.999', RoundingMode::Up)->minus('0.001'); // May fail
// Correct: Explicit rounding per operation
$rounded = $money->plus('0.999', RoundingMode::Up);
Currency Updates:
EUR). Pin to a minor version (e.g., 0.13.*) to avoid surprises:
composer require brick/money:^0.13
Floating-Point Assumptions:
Money to float/double for calculations. Use getAmount() (returns Number) or toRational() for exact arithmetic.Context Inheritance:
Context. To change it, explicitly pass a new context:
$money->toContext(new CustomContext(scale: 4));
Negative Values:
Money supports negatives, but some methods (e.g., allocate()) may behave unexpectedly. Test edge cases:
$negative = Money::of(-100, 'USD');
$negative->isNegative(); // true
Precision Issues:
toRational() for complex calculations to avoid intermediate rounding:
$result = $money->toRational()
->dividedBy(7)
->toContext($money->getContext(), RoundingMode::Down);
CurrencyMismatchException:
MoneyBag or operations. Use isSameValueAs() for currency-agnostic comparisons.RoundingNecessaryException:
RoundingMode when precision is required. Default to RoundingMode::HalfUp for financial rounding.Performance:
sudo apt-get install php-gmp php-bcmath
sudo systemctl restart apache2
Custom Rounding:
RoundingMode or implement RoundingBehavior for domain-specific rules:
use Brick\Math\RoundingMode;
class FinancialRoundingMode extends RoundingMode {
public static function create(): self { /* ... */ }
}
New Contexts:
Context:
use Brick\Money\Context;
class TaxContext extends Context {
public function __construct(public int $taxRate) {}
// Override methods as needed
}
MoneyBag Extensions:
MoneyBag:
class TaxedMoneyBag extends MoneyBag {
public function getTotalWithTax(int $taxRate): Money {
return $this->getTotal()->multipliedBy(1 + $taxRate / 100);
}
}
Currency Providers:
IsoCurrencyProvider for custom currency logic:
use Brick\Money\Currency\CurrencyProvider;
$provider = new CustomCurrencyProvider();
$currency = $provider->getCurrency('XYZ');
Laravel Helpers:
// app/Facades/MoneyFacade.php
namespace App\Facades;
use Brick\Money\Money;
use Illuminate\Support\Facades\Facade;
class MoneyFacade extends Facade {
protected static function getFacadeAccessor() { return 'money'; }
}
// app/Providers/AppServiceProvider.php
public function register() {
$this->app->singleton('money', function () {
return new class {
public function usd(float $amount): Money {
return Money::of($amount, 'USD');
}
};
});
}
Usage:
use App\Facades\Money;
$price = Money::usd(19.99);
How can I help you explore Laravel packages today?