maba/monetary

PHP金钱操作库

0.10.0 2020-03-21 10:23 UTC

This package is auto-updated.

Last update: 2024-09-21 20:20:47 UTC


README

不存储价格、利润或其他金额在浮点或整数变量中,而应使用Money值对象。它将金额与货币分组,因为不包含货币的金额不提供太多信息。此外,此库使用math库进行任意精度数学。因此,它使用字符串来存储货币单位。

为什么不使用浮点数?

  • 浮点数在执行金钱操作时不可靠,因为它们可能会丢失精度,并且可能丢失分。

为什么不使用整数?

  • 当达到最大整数值时,可能会发生疯狂的事情:它可能会被截断,甚至变成负数。这类错误可能是关键的,尤其是在处理金钱时。在32位系统(包括Windows上的任何系统)中,最大整数值为2147483647。由于这足够用于大多数情况,一些货币与其他货币相比相对较小(例如,2,147,483,647.00 BYR 约等于 150,000.00 EUR)。
  • 可能存在一些情况,其中金钱应计算或甚至保存而不进行四舍五入到最小单位。例如,非常小的佣金(分的一部分),当大量佣金相加时,可以增加到很大。
  • 当将金额作为整数存储时,在输出结果或进行任何其他操作之前,必须始终考虑货币除数(该货币中最小的可用单位)。例如,大多数货币的最小单位是分,而巴林第纳尔的最低单位是0.001,而日元根本没有分。或者,如果您总是以分的形式存储单位,就无法表示某些货币的最小单位。

架构

Money 是值对象 - 它是不可变的。换句话说,如果您需要更改金额或货币,只需创建另一个 Money 对象。同一个 Money 对象可以引用多个地方,因此仅更改此对象的字段可能会无意中更改金钱金额或货币在其他地方。

此外,Money 的默认实现没有任何逻辑 - 它仅包含金额和货币。所有操作都由服务执行。

  • 算术和比较操作由 MoneyCalculator 执行,它实现了 MoneyCalculatorInterface
  • 验证由 MoneyValidatorFinalMoneyValidator 执行,它们实现了 MoneyValidatorInterfaceFinalMoneyValidator 断言 Money 不包含比最小货币单位更小的部分。
  • Money 对象的创建由 MoneyFactory 执行,它实现了 MoneyFactoryInterface

为什么不使用自包含逻辑?

这种方法可以轻松更改逻辑和表示金钱的类。

例如,您可以使用某个自定义 Money 类 - 此库通过 MoneyInterface 对象执行操作。由于 MoneyFactoryInterface 总是用于创建新实体,因此您可以更改任何 Money 操作的结果类。

// ...
// lots of code using MyMoney class
// ...

class MyMoney implements MoneyInterface
{
    // ...

    public function getAmount()
    {
        // ...
    }

    public function getCurrency()
    {
        // ...
    }
}

class MyMoneyFactory implements MoneyFactoryInterface
{
    // create instances of MyMoney here
}

// construct $calculator with instance of MyMoneyFactory

$result = $calculator->add(new MyMoney('1', 'EUR'), new Money('2.12', 'EUR'));  // you can mix classes, too
// $result is instance of MyMoney now

另一个例子是 MoneyCalculatorInterface 的实现,它允许您添加具有不同货币的 Money 对象,自动进行货币兑换操作。

安装

composer require maba/monetary

使用方法

use Maba\Component\Math\BcMath;
use Maba\Component\Math\Math;
use Maba\Component\Math\NumberFormatter;
use Maba\Component\Math\NumberValidator;
use Maba\Component\Monetary\Factory\MoneyFactory;
use Maba\Component\Monetary\Formatting\FormattingContext;
use Maba\Component\Monetary\Formatting\MoneyFormatter;
use Maba\Component\Monetary\Information\MoneyInformationProvider;
use Maba\Component\Monetary\Validation\MoneyValidator;
use Maba\Component\Monetary\Money;
use Maba\Component\Monetary\MoneyCalculator;

// set up dependencies. I would really suggest to use DIC here
$math = new Math(new BcMath());
$informationProvider = new MoneyInformationProvider();
$factory = new MoneyFactory($math, new MoneyValidator($math, $informationProvider, new NumberValidator()));
$calculator = new MoneyCalculator($math, $factory, $informationProvider);

// make math operations on Money objects
$result = $calculator->ceil(
    $calculator->mul(
        $calculator->mul(
            $calculator->add(new Money('12.23', 'EUR'), new Money('32.12', 'EUR')),
            879134421.2183
        ),
        12.33
    )
);

// compare Money objects
if ($calculator->isGt($result, $factory->createZero())) {
    // format Money objects as strings
    $formatter = new MoneyFormatter(
        $calculator,
        $informationProvider,
        new NumberFormatter($math),
        array('EUR' => ''),        // optional - symbols to replace currency codes
        '%currency%%amount%'        // optional - custom template
    );

    // set up context. $context argument is optional and needed only if defaults need to be changed
    $context = new FormattingContext();
    $context->setThousandsSeparator(' ');
    // set other formatting options in the context if needed
    echo $formatter->formatMoney($result, $context); // outputs €480 741 910 794.12
}

运行测试

Travis status Coverage Status

composer install
vendor/bin/phpunit