moneyphp/money
moneyphp/money is a PHP value-object library for safe money handling without floats. Uses string-based big integers, supports arithmetic, allocation, currencies/ISO repositories, formatting (incl. intl), JSON serialization, and exchange rates. Requires BCMath.
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require moneyphp/money
Ensure bcmath extension is enabled in your PHP configuration.
First Use Case:
Create a Money object for a specific currency:
use Money\Money;
$amount = Money::EUR(500); // 5.00 EUR
Key Classes to Know:
Money: Core value object for monetary amounts.Currency: Represents currencies (e.g., Currency::EUR).MoneyFactory: Static factory for creating Money objects.Converter: Handles currency conversions.Where to Look First:
src/Money/Money.php (core Money class).src/Money/Currency.php (currency definitions).$fiveEur = Money::EUR(500); // 5.00 EUR (subunits)
$tenUsd = Money::USD(1000); // 10.00 USD
$sum = $fiveEur->add($tenUsd); // Throws exception (currencies must match)
$sumSameCurrency = $fiveEur->add(Money::EUR(300)); // 8.00 EUR
$half = $fiveEur->divide('2'); // 2.50 EUR
$double = $fiveEur->multiply('2'); // 10.00 EUR
$converter = new Money\Converter($exchangeRates);
$converted = $converter->convert($fiveEur, Currency::USD);
Converter with Exchange Rates:
$exchangeRates = [
'EUR' => ['USD' => '1.10'], // 1 EUR = 1.10 USD
];
$converter = new Money\Converter($exchangeRates);
use Money\Currency\ISOCurrencies;
use Money\Exchange\Swap;
$swap = new Swap(ISOCurrencies::getInstance());
$rate = $swap->getRate('EUR', 'USD');
$formatter = new Money\Formatter\IntlMoneyFormatter(
'en_US',
IntlMoneyFormatter::CURRENCY_FORMATTING
);
echo $formatter->format($fiveEur); // "$5.00"
$parser = new Money\Parser\DecimalMoneyParser();
$money = $parser->parse('5.00', Currency::USD);
$amounts = [Money::EUR(100), Money::EUR(200), Money::EUR(300)];
$sum = Money::sum($amounts); // 6.00 EUR
$avg = Money::avg($amounts); // 2.00 EUR
$tenEur = Money::EUR(1000);
list($part1, $part2, $part3) = $tenEur->allocate([1, 1, 1]);
// part1: 3.34 EUR, part2: 3.33 EUR, part3: 3.33 EUR (due to rounding)
use Money\Currency\CryptoCurrencies;
$bitcoin = Money::BTC('100000000'); // 1.00 BTC (subunits: satoshis)
$cryptoCurrencies = new CryptoCurrencies();
$ethereum = Money::ETH('500000000000000000', $cryptoCurrencies->get('ETH'));
Service Container Binding:
Bind MoneyFactory, Converter, or ISOCurrencies in AppServiceProvider:
public function register()
{
$this->app->singleton(Money\Currency\ISOCurrencies::class, function () {
return Money\Currency\ISOCurrencies::getInstance();
});
}
Request Validation:
Use moneyphp/money with Laravel's validation:
use Money\Currency\ISOCurrencies;
use Money\Parser\DecimalMoneyParser;
$parser = new DecimalMoneyParser();
$currency = ISOCurrencies::getInstance()->get('USD');
$validated = request()->validate([
'amount' => ['required', function ($attribute, $value, $fail) use ($parser, $currency) {
try {
$money = $parser->parse($value, $currency);
} catch (\Exception $e) {
$fail('Invalid monetary value.');
}
}],
]);
Database Storage:
Store Money objects as strings (e.g., "EUR:500") or use JSON serialization:
$money = Money::EUR(500);
$serialized = json_encode($money); // '{"amount":"500","currency":"EUR"}'
$deserialized = Money::fromArray(json_decode($serialized, true));
API Responses:
Format Money objects in responses:
return response()->json([
'total' => (new Money\Formatter\DecimalMoneyFormatter())->format($totalMoney),
]);
Testing:
Use Money\Comparator for assertions:
use Money\Comparator;
$this->assertTrue(Comparator::equals($money1, $money2));
Floating-Point Avoidance:
Money methods (e.g., Money::EUR(5.5)) will throw an exception.$money = Money::EUR('5.50'); // Correct
$money = Money::EUR(550); // Correct (subunits)
Currency Mismatches:
add, subtract) require matching currencies. Mixing currencies throws an exception.Converter:
$converter = new Money\Converter($exchangeRates);
$converted = $converter->convert($eurMoney, Currency::USD);
Subunit Precision:
JPY) have no subunits (e.g., 1 subunit = 1 currency). Operations like divide may behave unexpectedly.roundToUnit or handle edge cases:
$yen = Money::JPY(100);
$half = $yen->divide('2'); // 50 JPY (no fractional part)
BCMath/GMP Dependencies:
BCMath for calculations. If BCMath is unavailable, operations may fail or fall back to slower methods.bcmath is enabled in php.ini or configure a fallback calculator:
$calculator = new Money\Calculator\GmpCalculator();
$money = new Money\Money('1000', Currency::USD, $calculator);
Negative Values:
allocate).if ($money->isNegative()) {
// Handle negative case
}
JSON Serialization:
Money objects serialize to arrays by default, which may not match your API expectations.JsonSerializable:
$serialized = json_encode([
'amount' => $money->getAmount(),
'currency' => $money->getCurrency()->getCode(),
]);
Currency Code Case:
How can I help you explore Laravel packages today?