Money Bundle
MAINTAINER WANTER - issue reports and pull requests will not be actively investigated. I am happy to advise, but I'm not longer actively using/maintaing this library. Please let me know if you're interested in taking over ownership of the repo., (*1)
Overview
Symfony2 Bundle wrapping common Money and Currency related needs such as integer-based math, currency codes, and money conversion., (*2)
Installation
Add the package to your composer.json file:, (*3)
{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/matmar10/money-bundle
}
],
"require": {
"matmar10/money-bundle": "dev-master"
}
}
Creating Objects
Currencies are identified by a currency code and have a calculation and display precision:, (*4)
$eur = new Currency('EUR', 2, 2);
$euros = new Money($eur);
$euros->setAmountFloat(1.99);
Basic Math
The Money object wraps all basic math functions using underlying integer math
to avoid the (problems with floating point math)[http://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency]., (*5)
All amounts are stored as integer values internally using
the calculation precision as the scale., (*6)
// must use a valid iso4217 currency code
// with the exception of BTC for Bitcoin, as specified in the override configuration
$usd = new Currency('USD', 5, 2);
$usdAmount1 = new Money($usd);
$usdAmount1->setAmountFloat(1.2345);
$usdAmount2 = new Money($usd);
$usdAmount2->setAmountFloat(1.2345);
$usdAmount1->isEqual($usdAmount2); // true
$resultAmount1 = $usdAmount1->add($usdAmount2);
echo $resultAmount1->getAmountDisplay(); // 2.47
$resultAmount2 = $usdAmount1->subtract($usdAmount2);
echo $resultAmount2->getAmountFloat(); // 0
$resultAmount3 = $usdAmount1->multiply(3);
echo $resultAmount3->getAmountFloat(); // 3.7035
echo $resultAmount3->getAmountDisplay(); // 3.70
$resultAmount4 = $usdAmount1->divide(2);
echo $resultAmount3->getAmountFloat(); // 0.61725
echo $resultAmount3->getAmountDisplay(); // 0.62
Dealing with Fractional Cents
How do you divide $10 evenly amongst three people?
In reality, you can't divide fractional cents., (*7)
Really, you want to end up with three equal-ish shares:, (*8)
$eurAmount = new Money(new Currency('EUR', 2, 2));
$eurAmount->setAmountFloat(10);
// split the 10 euros into three equal parts using euro cents as the smallest unit
$shares = $eurAmount->allocate(array(1, 1, 1), 2);
$shares[0]->getAmountFloat(); // 3.34
$shares[1]->getAmountFloat(); // 3.33
$shares[2]->getAmountFloat(); // 3.33
Converting Between Currencies
Use the CurrencyPair
object to convert between disparate currencies using an exchange rate:, (*9)
Note that the rate works bi-directionally:, (*10)
$gbp = new Currency('GBP', 2, 2);
$usd = new Currency('USD', 2, 2);
$gbpAmount = new Money($gbp);
$gbpAmount->setAmountFloat(10);
// 1 GBP = 1.5 USD
$gbpToUsd = new CurrencyPair($gbp, $usd, 1.5);
$usdAmount = $gbpToUsd->convert($gbpAmount);
echo $usdAmount->getDisplay(); // 15.00
$gbpAmount2 = $gbpToUsd->convert($usdAmount);
echo $gbpAmount2->getDisplay(); // 10.00
Currency Manager Service
Instead of building up currencies and money objects manually all the time,
consider using the lmh_money.currency_manager
service that is registered
into Symfony's dependency injection container., (*11)
The manager supports providing an ISO country or currency code:, (*12)
// inside a Symfony controller, for example
$manager = $this->getContainer()->get('lmh_money.currency_manager');
$amount = $manager->getMoney('ES');
echo $amount->getCurrency(); // EUR
$amount->setAmountFloat(100);
// 1 USD = 0.75 EUR
$pair = $manager->getPair('US', 'ES', 0.75);
$converted = $pair->convert($amount);
echo $converted->getAmountDisplay(); // 75.00
Adding New Currencies
You can add new currencies that are not supported by default.
This is useful, for example to add alternative currencies such as Litecoin and Ripple, (*13)
// inside a config file, such as app/config/config.yml
matmar10_money:
currencies:
LTC: { displayPrecision: 5, calculationPrecision: 8, symbol: 'Ł' }
XRP: { displayPrecision: 8, calculationPrecision: 8 }
Currency Alias
Alias handling for currencies, to set the currency settings "Euro" equel to "EUR", extend the config like this:, (*14)
matmar10_money:
currencies:
Euro:
alias: EUR
Currency Validator
The Bundle also includes a Symfony validator for use in validating an entity's attribute is a valid currency code
using (Symfony's Validator component)[https://github.com/symfony/Validator]., (*15)
Example use of the annotation:, (*16)
<?php
// inside an entity file, such as src/Bundle/AcmeBundle/Entity/Purchase.php
namespace Acme\Bundle\AcmeBundle\Entity;
use Lmh\Bundle\MoneyBundle\Validator\Constraints as Assert;
class Purchase
{
/**
* @Assert\CurrencyCode()
*/
public $currency;
}
Using in the validator (see (Validator Component docs for details)[https://github.com/symfony/Validator] ):, (*17)
<?php
use Acme\Bundle\AcmeBundle;
use Symfony\Component\Validator\Validation;
$validator = Validation::createValidatorBuilder()
->enableAnnotationMapping()
->getValidator();
$purchase = new Purchase()
$purchase->currency = 'invalid-this-is-not-a-code';
$violations = $validator->validate($purchase);
Embedded Money object in a Doctrine Entity
Money object is prepared to handle doctrine embedded style: http://doctrine-orm.readthedocs.org/en/latest/tutorials/embeddables.html, (*18)
Add this to your config:, (*19)
doctrine:
orm:
mappings:
Matmar10MoneyBundle:
type: annotation
dir: %kernel.root_dir%/../vendor/matmar10/lib-money/src/Matmar10/Money/Entity
is_bundle: false
prefix: 'Matmar10\Money\Entity'
Inside your entity:, (*20)
<?php
namespace foo\bar;
use Doctrine\ORM\Mapping as ORM;
class Invoice {
/**
* @var \Matmar10\Money\Entity\Money
* @ORM\Embedded(columnPrefix="price_", class="Matmar10\Money\Entity\Money")
*/
private $price;
}
your table should now these two new columns:, (*21)
- price_amount // price as INTEGER value
- price_currency // currency code like "EUR"