Git Product home page Git Product logo

money's Introduction

Money

Latest Version GitHub Workflow Status Total Downloads

Email

Money PHP

PHP library to make working with money safer, easier, and fun!

"If I had a dime for every time I've seen someone use FLOAT to store currency, I'd have $999.997634" -- Bill Karwin

In short: You shouldn't represent monetary values by a float. Wherever you need to represent money, use this Money value object. Since version 3.0 this library uses strings internally in order to support unlimited integers.

<?php

use Money\Money;

$fiveEur = Money::EUR(500);
$tenEur = $fiveEur->add($fiveEur);

list($part1, $part2, $part3) = $tenEur->allocate([1, 1, 1]);
assert($part1->equals(Money::EUR(334)));
assert($part2->equals(Money::EUR(333)));
assert($part3->equals(Money::EUR(333)));

The documentation is available at http://moneyphp.org

Requirements

This library requires the BCMath PHP extension. There might be additional dependencies for specific feature, e.g. the Swap exchange implementation, check the documentation for more information.

Version 4 requires PHP 8.0. For older version of PHP, use version 3 of this library. From version 4.5 this package will only support PHP versions that actually receive updates by PHP itself. If you want to use the package with older PHP versions, you can of course use older versions of this package.

Install

Via Composer

$ composer require moneyphp/money

Features

  • JSON Serialization
  • Big integer support utilizing different, transparent calculation logic upon availability (bcmath, gmp, plain php)
  • Money formatting (including intl formatter)
  • Currency repositories (ISO currencies included)
  • Money exchange (including Swap implementation)

Documentation

Please see the official documentation.

Testing

We try to follow TDD by using phpunit to test this library.

$ composer test

Running the tests in Docker

Money requires a set of dependencies, so you might want to run it in Docker.

First, build the image locally:

$ docker build -t moneyphp .

Then run the tests:

$ docker run --rm -it -v $PWD:/app -w /app moneyphp vendor/bin/phpunit --exclude-group segmentation

Contributing

We would love to see you helping us to make this library better and better. Please keep in mind we do not use suffixes and prefixes in class names, so not CurrenciesInterface, but Currencies. Other than that, Style CI will help you using the same code style as we are using. Please provide tests when creating a PR and clear descriptions of bugs when filing issues.

Security

If you discover any security related issues, please contact us at [email protected].

License

The MIT License (MIT). Please see License File for more information.

Acknowledgements

This library is heavily inspired by Martin Fowler's Money pattern. A special remark goes to Mathias Verraes, without his contributions, in code and via his blog, this library would not be where it stands now.

money's People

Contributors

bendavies avatar camspiers avatar chris53897 avatar chris8934 avatar driesvints avatar firehed avatar frederikbosch avatar gmponos avatar half0wl avatar hollodotme avatar jaikdean avatar jongotlin avatar marijn avatar mathiasverraes avatar michaelgooden avatar ocramius avatar pamil avatar pascal-hofmann avatar peterdevpl avatar piotrantosik avatar pmjones avatar rgeraads avatar ricardogobbosouza avatar rodnaph avatar rogervila avatar ruudk avatar sagikazarmark avatar teohhanhui avatar thewilkybarkid avatar umpirsky avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

money's Issues

There is any way to convert to string?

Hello guys, I'm trying to convert 1000 to R$10,00.
I read the docs and didn't found the function on this lib, am I wrong or this lib doesn't support this conversion?

Thanks in advance.

Money::stringToUnits(".99") returns 9900, not 99

I was going through the motions on 'what would a user do' as far as input goes that might trip up the ::stringToUnits() function.

Tested on nextrelease and master, calling Money::stringToUnits(".99") results in a value of 9900.
Personally I'd expect 99 back, maybe even an exception, but it just silently converts to 9900.

Thought it was worth mentioning even if you decide it's not a problem... thanks for the work on this!

greaterThanOrEqual and lessThanOrEqual give incorrect values and have no tests

Just stumbled upon this when writing tests for my own project.

greaterThanOrEqual() and lessThanOrEqual() give the inverse results to what you would expect.

Given compare():

    public function compare(Money $other)
    {
        $this->assertSameCurrency($other);
        if ($this->amount < $other->amount) {
            return -1;
        } elseif ($this->amount == $other->amount) {
            return 0;
        } else {
            return 1;
        }
    }

Then we'd expect gtoe:

    public function greaterThanOrEqual(Money $other)
    {
        return 0 <= $this->compare($other);
    }

and ltoe:

    public function lessThanOrEqual(Money $other)
    {
        return 0 >= $this->compare($other);
    }

Instead, actual gtoe:

    /**
     * @param \Money\Money $other
     * @return bool
     */
    public function greaterThanOrEqual(Money $other)
    {
        return 0 >= $this->compare($other);
    }

actual ltoe:

    /**
     * @param \Money\Money $other
     * @return bool
     */
    public function lessThanOrEqual(Money $other)
    {
        return 0 <= $this->compare($other);
    }

Additionally, there are no tests for this behaviour, which is likely the only reason this was allowed to happen at all:

    public function testComparison()
    {
        $euro1 = new Money(1, new Currency('EUR'));
        $euro2 = new Money(2, new Currency('EUR'));
        $usd = new Money(1, new Currency('USD'));

        $this->assertTrue($euro2->greaterThan($euro1));
        $this->assertFalse($euro1->greaterThan($euro2));
        $this->assertTrue($euro1->lessThan($euro2));
        $this->assertFalse($euro2->lessThan($euro1));

        $this->assertEquals(-1, $euro1->compare($euro2));
        $this->assertEquals(1, $euro2->compare($euro1));
        $this->assertEquals(0, $euro1->compare($euro1));
    }

Provide IDE hints for static methods (eg, Money::USD(5000))

The Money library allows the currency to be used as a static method, ie:

$money = Money::USD(5000);

However, these static methods are dynamic and picked up by Money::__callStatic, which means there's no IDE hinting, so the IDE doesn't know it exists, and won't infer that $money is a Money type.

The methods can be annotated on the Money class, though, like in beberlei/assert, and this will provide IDE integration.

If this is something which you would be interested in @mathiasverraes, I'll implement it then submit a PR :)

QA : How can I use this on a form

Hello,

Suppose I have a form where I can enter a amount that a user has to pay for a plan
How can I then use the money object instead of a float or text.

Roelof

Fractions of a cent

Does this work for fractions of a cent? How accurate is it if I use the multiply and pass in something like 0.1234?

Symfony Intl

How about using Symfony Intl component instead static values for currencies?

Symfony\Component\Intl\Intl::getCurrencyBundle()->getCurrencyNames();

lessThanOrEqual not working correctly?

Consider this output:

checking if this value: 15367555 is less than 0
it is less!

For this code:

echo "checking if this value: ".$value->getAmount()." is less than ".Money::EUR(0)->getAmount();

if ($value->lessThanOrEqual(Money::EUR(0))) {
    echo "<br> it is less!";
} else {
    echo "<br> no its not";
}
die();

Deal with decimals using PreciseMoney

idea from http://joda-money.sourceforge.net/userguide.html

There are two main classes which are both based on BigDecimal:
Money - an amount in a single currency where the decimal places is determined by the currency
BigMoney - an amount in a single currency where there is no restriction on the scale

For example, the currencies of US dollars, Euros and British Pounds all use 2 decimal places, thus a Money instance for any of these currencies will have 2 decimal places. By contrast, the Japanese Yen has 0 decimal places, and thus any Money instance with that currency will have 0 decimal places.

Instances of BigMoney may have any scale that can be supported by BigDecimal.
Conversion between a Money and a BigMoney can be performed using the methods toBigMoney() and toMoney(). The latter optionally takes a rounding mode to handle the case where information must be lost.

add 'unitsToString' function to Money\Money

For legacy systems I need to convert to/from floats. To convert I do the following:

use Money\Money;
use Money\Currency;
$float = 123.34;
$money = new Money(Money::stringToUnits((string)$float), new Currency('USD'));
var_dump($money);
/*
object(Money\Money)#2 (2) {
  ["amount":"Money\Money":private]=>
  int(12334)
  ["currency":"Money\Money":private]=>
  object(Money\Currency)#3 (1) {
    ["name":"Money\Currency":private]=>
    string(3) "USD"
  }
}
*/

$float = $money->getAmount() / 100;
var_dump($float);
/*
float(123.34)
*/

For example money_format() expects the parameter to be a float: http://php.net/manual/en/function.money-format.php

Shouldn't it make sense to have a unitsToString() in Money\Money? Maybe even
unitsToFloat() and floatToUnits() .

I can create a pull-request for this if you would consider including this change.

Use of interfaces

We should using interfaces for Money, Currency, etc.
I prefer MoneyValue for Money.

Counter currency and base currency seem reversed

I don't think that it makes any different in the actual calculations, but it appears that the first currency in "EUR/USD" is referred to as the base currency and the second the counter currency. But when we create the CurrencyPair from the ISO string we pass "EUR" in as the counter currency. I don't think this affects anything but it does mean the that variables are named incorrectly and appear to need to be switched.

About method add in Money class[Code Readability]

public function add(Money $money)
{
        $this->assertSameCurrency($money);

        return new self($this->amount + $money->amount, $this->currency);
}

I think method "add" should do what it expected to do and only add two moneys and increase amount. It is for code readability.


 public function add(Money $money)
 {
        if($this->isSameCurrency($money))
        {
            $this->amount += $money->amount;
        }
 }

Handle currencies customization

Hey,

Actually, I want to add a new currency, for example: facebook credit.
I have many options:

1 Do not rely directly on Money.
* Custom\Currency extends Custom\Currency override the __construct.
* Custom\Money extend Money\Money override the __construct and the __callStatic replace the Currency typehinting by a Custom\Currency

2 Rely on Money

  • Use the Currency and add my custom currency data into the currencies file
  • In the first case, it looks a bit overkill but maybe it's the best practice (I would love a feedback)?
  • In the second case, it's straightforward yet it requires a hack in currencies.php file which is not versioned but live in the vendor dependency directory (and that sucks).
    Can we rely on a global var / path ? some other stuffs ?

Version to work with

I'm in the means of using this project in one of my own. Now I'm reading that a new version will break backwards compatibility, but the "nextrelease" branch was last updated in February. Is there still work being done or should I better use the older released version?

Provide a way to do lazy-loading of currencies

The library is quite useful and can be integrated seamlessly in many domain.

The major issue I found is that the library rely on a currencies.php file, which load all the currencies. This, IMO have two drawbacks:

  • currencies cannot be lazy loaded, and therefore
  • currencies cannot be easily stored on database (or equivalent).

I think it would be better to return a simple object with a "load" method, similar to what is done by composer.

e.g.

// CurrencyLoader.php
interface CurrencyLoader {
    public function load($name);
//    public function loadByIso($iso);
}

// currencies.php
final class DefaultCurrencyLoader {
    private static $currencies = array(
        "AED" => "United Arab Emirates Dirham",
        "AFN" => "Afghan Afghani",
        // many other
    );

    public function load($name)
    {
         if (! isset(self::$currencies[$name])
            throw new UnknownCurrencyException();
         return self::$currencies[$name];
    }
);

I know that Money are threated as final immutable classes, and I agree, but currencies are a bit different IMO.

What do you think? Should I open a PR? :)

dealing with taxes

Hi,

When you use Money, most of case you have to deal with taxes and like currencies you have to apply a conversion between price with tax and price without tax.

So I test this library (the nextrelease branch) and if I want to swith from a price tax to an untax price I have to myltiply or divide it.

Example :

Price with tax : 3,99 €
tax rate : 20%
so for retrieving the untax price :

use Money\Money;

/** @var \Money\Money $start */
$start = Money::EUR(399);

$ht = $start->divide(1.2);

echo $ht->getAmount()."\n";

output : 333 ( 3.33€)

Cool, now with my untax price I can retrieve my price with tax :

/** @var \Money\Money $start */
$start = Money::EUR(333);

$ht = $start->multiply(1.2);

echo $ht->getAmount()."\n";

output : 400 (4.00 €)

question

  • Using integer is it really the best way ?
  • How to use this library with taxes (in my example I use a really simple case)

Thanks

currency conversion api

Could look like the joda-money api, but using CurrencyPair:

// convert to GBP using a supplied rate
BigDecimal conversionRate = ...; // obtained from code outside Joda-Money
Money moneyGBP = money.convertTo(CurrencyUnit.GBP, conversionRate);

Doctrine value objects

As we have value object support in doctrine - I am wondering that the ability to use Money directly in entities as an embeddable would come quite handy in some cases.

Unable to extend Money

I'm unable to extend Money because of methods like this:

public function divide($divisor, $rounding_mode = self::ROUND_HALF_UP)
{
    $this->assertOperand($divisor);
    $this->assertRoundingMode($rounding_mode);

    $quotient = (int) round($this->amount / $divisor, 0, $rounding_mode);

    return new Money($quotient, $this->currency);
}

Which instantiate a new Money object. Would you be open to a PR changing these occurrences to this?

public function divide($divisor, $rounding_mode = self::ROUND_HALF_UP)
{
    $this->assertOperand($divisor);
    $this->assertRoundingMode($rounding_mode);

    $quotient = (int) round($this->amount / $divisor, 0, $rounding_mode);

    return new static($quotient, $this->currency);
}

So we can extend the Money object and not have lots of things break when we depend on our subclass?

Add filter to convert decimal notations to integer notations in cents

Avoid using float conversions as this can cause issues.

In a client project, we used this solution (which is incomplete, but served our immediate purpose).

<?php
namespace Custom\Filter;

class CurrencySave implements \Zend_Filter_Interface
{
    /**
     * Convert belgian currency format to DB value (cents)
     *
     * @param string $value
     * @return int
     */
    public function filter($value)
    {
        $value = trim($value);

        $withoutDecimals = '#^\d*$#';
        $oneDecimal = '#^\d*[\.,]\d$#';
        $twoDecimals = '#^\d*[\.,]\d\d$#';

        if(preg_match($withoutDecimals, $value)) {
            return (int) ($value.'00');
        }

        if(preg_match($oneDecimal, $value)) {
            return (int) (str_replace(array(',', '.'), '', $value).'0');
        }

        if(preg_match($twoDecimals, $value)) {
            return (int) (str_replace(array(',', '.'), '', $value));
        }

        throw new \InvalidArgumentException("Not a valid currency amount");
    }
}

/* Quick testing code:

$filter = new CurrencySave();
assert($filter->filter('1') === 100);
assert($filter->filter('1.0') === 100);
assert($filter->filter('1.00') === 100);
assert($filter->filter('12.99') === 1299);
assert($filter->filter('1,0') === 100);
assert($filter->filter('1,00') === 100);
assert($filter->filter('12,99') === 1299);

//*/

Comparsion problems.

Try this code:

$JohnsCash = Money::USD(0.9);
$MikesCash = Money::USD(0.7);

// John lends money to Mike - they want to have same amount of money
$JohnsCash = $JohnsCash->subtract(Money::USD(0.1));
$MikesCash = $MikesCash->add(Money::USD(0.1));

echo "John's money: ".$JohnsCash.'<br>';
echo "Mike's money: ".$MikesCash.'<br>';


if($JohnsCash->equals($MikesCash)) {
    echo "They have same amount of money :)";
} else {
    echo "They don't have same amount of money :(";
}

Guess what output will be?

You don't even get Bill Karwin's joke, that you have in your README.

The point is to store amount of cents separated - using Integer instead of Float (which is used in this library).

clean up exceptions

replace \Money\InvalidArgumentException with a native InvalidArgumentException,
use namespaced exception where they actually provide value.

Following zeroses cannot be parsed as money version 1.2.1

The following code throws (worked fine before 1.2.1):
//Money\InvalidArgumentException: The value could not be parsed as money

    public function test_converts_price_with_many_following_zeroes()
    {
        $result = $this->convertMoney("111.930000000000000000000000000000");
        $this->assertEquals(Money::USD(11193), $result);
    }

    private function convertMoney($price)
    {
        $units = Money::stringToUnits($price);
        return Money::USD($units);
    }

Division by zero

So...

<?php

require_once "vendor/autoload.php";

$tenDollars = Money\Money::USD(10);

$divided = $tenDollars->divide(0);

echo $divided->getAmount();

... produces 0 and a warning, no exception. Is that by design?

Reason for absence of __toString

Hello,

I was wondering if there is a reason that there isn't a __toString method on the Money\Money object ?

Instead of doing in the view :

echo $product->price->getAmount() / 100 . ' ' . $product->price->getCurrency()

We could do :

echo $product->price

With the following __toString method:

public function __toString()
{
    $formatter = new \NumberFormatter('nl_NL', \NumberFormatter::CURRENCY);

    return $formatter->format($this->getAmount() / 100);
}

Thank you,

Update: I see from this issue that this shouldn't be done here apparently. Any recommended way of handling this, which would remove this logic from the view ?

Bugfix release

Issue #43 talks about the next major release (in it you mention 3.0, but seeing the current releases that should be 2.0 I think?). However there are a number of commits that contain important bug fixes since the last release, which are not available if you want to use semver in a project.

Until 2.0/3.0 is out, could you tag a new patch release (1.2.2) with the bugfixes?

How to deal with integer limits?

Hi, we have to deal with Money and store it in database. In that process we came up with a few issues that I'd like to discuss so that maybe we can find "best practices" on how to deal with all that.

First there's the problem of integer limits in memory. On a 32bits system the amount will be stored as an integer up to something like 21 million $/€/… (because it's stored as cents). That's not much and can be a real problem in some applications, I wonder if that should be mentioned in the README? Is the Money class only appropriate for 64b systems (where the limit is much less of a problem for most cases)? Should the amount be stored differently on 32b? (for our team it won't be a problem because we use only 64 bits systems)

Then there's the question of how to store it in database. For example in MySQL you can use the integer type which has the same limit as a 32b PHP integer. So obviously it's much better to use BIGINT which is the equivalent of a 64b integer.

However if you use Doctrine (quite popular in the PHP world), the entity manager will map a SQL BIGINT to a PHP string in order to be compatible with 32b systems… So Money will end up with a string instead of an int as the amount, which is an issue I guess because the class expects to have an int there (I expect things to break/not behave correctly). So we have several options, like storing the whole Money object serialized (we see that the class even implements Serializable in the next version) but it comes with a lot of other issues, creating custom Doctrine types to map SQL BIGINT to actual PHP integers (i.e. it assumes a 64b system is used), store the object as a string (e.g. 12 EUR), etc. Is there some kind of best practices for that? Obviously storing as integer/bigint is great because it allows filtering, ordering, etc in SQL queries, but is that a big "no no" (just like storing money in float is a big "no no")?

Default currency

Hello,

I think the Money library should have a default currency just as DateTime has date_default_timezone. Without this, I need to inject a provider or the default currency in everywhere that I need to use the Money type:

class Product
{
    /**
     * @Column(type="money")
     */
    protected $price;

    public function __construct()
    {
        $this->price = new Money(0);
    }
}
// throw new UnspecifiedCurrencyException()
$price = new Money(15);

// Initialization
Money::setDefaultCurrency('USD');

$price = new Money(15);
assert(strval($price->getCurrency()), 'USD');

/*
 * To discuss:
 * throw new CurrencyAlreadyDefinedException();
 */
Money::setDefaultCurrency('BRL');  

If you agree, I would be happy to contribute with this feature.

Add MoneyConverter and CurrencyPairRepository?

We use this money library in our application. We introduced a service that is able to convert a given money type to a given other currency using the CurrencyPair abstraction in this library. Are you interested in a PR to add it to this library instead of letting it live in our application alone? In short it looks like this:

MoneyConverter

<?php
class MoneyConverter
{
    function __construct(CurrencyPairRepository $repository);
    function convert(Money $money, Currency $currency);
}

CurrencyPairRepository (we implemented this with DB lookups, the lib could ship an implementation that takes an array of CurrencyPairs)

<?php
abstract class CurrencyPairRepository
{
    abstract public function findBy(Criteria $criteria);
}

Criteria

class Criteria
{
    function getBaseCurrency();
    function getCounterCurrency();
}

ping @ebroekmeulen :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.