brick / money

A money and currency library for PHP
MIT License
1.65k stars 102 forks source link

[feature request] Allow changing default rounding mode #56

Closed AlexeyKosov closed 2 years ago

AlexeyKosov commented 2 years ago

Some methods like of(), dividedBy(), multipliedBy(), in some cases will throw an exception if there's no rounding mode explicitly specified so I have to call them with RoundingMode::HALF_EVEN (which is the default for the app).

The corresponding method calls look like

$amount = Money::of($float, $currency, roundingMode: RoundingMode::HALF_EVEN)
     ->multipliedBy($percent, RoundingMode::HALF_EVEN);
     ->dividedBy($anotherPercent, RoundingMode::HALF_EVEN)
;

As you see, the same rounding mode repeats many times which violates the DRY principle and makes the code too busy. And it turns out, in my project, there's almost a hundred mentions of this rounding mode. It would be great if there was a way to set a default project-wide rounding mode so that there's no need to repeat it every time as in the example above.

Maybe some static method e.g. Money::setDefaultRoundingMode()

class Money
{
    private static $defaultRoundingMode = RoundingMode::UNNECESSARY;

    public static function setDefaultRoundingMode(int $roundingMode)
    {
        self::$defaultRoundingMode = $roundingMode;
    }

    public static function create(BigNumber $amount, Currency $currency, Context $context, int $roundingMode = null) : Money
    {
        $amount = $context->applyTo($amount, $currency, $roundingMode ?? self::$defaultRoundingMode);

        return new Money($amount, $currency, $context);
    }   
}

I do realize that the aim of this library is to always return exact results, unless you're explicitly instructing it not to. But when the library is used for financial apps, where proper rounding is a must, it just becomes a PITA to always specify the same rounding mode.

It would have been also an option to extend the Money class with one having default rounding mode other than 'unneccessary', but the class is 'final'.

BenMorel commented 2 years ago

Hi, setting any kind of global default (as a static variable) is something I'm fighting against, so I'm not willing to implement this.

A middle ground could be to set a default rounding mode in the Money instance itself (not static), so that you only have to specify it at object instantiation:

$amount = Money::of($float, $currency, roundingMode: RoundingMode::HALF_EVEN)
     ->multipliedBy($percent);
     ->dividedBy($anotherPercent)
;

But this is probably not something I'll do in brick/money. If you're willing to implement this yourself, your best bet is probably to create your own Money class that wraps Brick\Money\Money, i.e. use composition instead of inheritance. This way you have full control over your public API, and can change the behaviour to include a default rounding mode, globally or per instance!