0.3.6 2022-08-24 11:11 UTC

README

Latest Version Build Status Coverage Status

优雅的货币实现

这个库提供了使用纯面向对象方法进行货币操作的类。

为什么又是PHP-money?

为什么我们又要制作另一个PHP的货币库?毕竟,已经有了

但是,在使用纯面向对象开发时存在一些缺点。

  • 没有接口——我们无法创建一个表示货币本身的对象。
  • 所有现有的实现都是具有数百行代码和数十个方法(如addsubdividecompare等)的类,这些方法本质上属于过程式风格。
  • 因为没有接口,所以我们无法添加新功能,并且大多数实现都是最终的。
  • 急切执行——所有计算都在构造时发生,因为构造函数的参数应该被评估。
  • 现有库隐藏了刻度,我们唯一能控制的是舍入方法。

我们的解决方案

  • 我们有Money接口!这种实现不是过程式DTO。
  • 没有数百行代码的类和数十个方法。每个操作都是一个实现了Money接口的对象。
  • 延迟执行:您可以构建复杂的表达式而无需立即计算。
  • 显式刻度以避免模糊的结果。

接口的存在允许您创建不同的类。例如,您可以轻松实现UserBalance,它可以从数据库或某些API或甚至从文件中获取数据。

<?php

declare(strict_types=1);

use ElegantBro\Money\Currencies\USD;
use ElegantBro\Money\Currency;
use ElegantBro\Money\Money;

final class UserBalance implements Money
{
    /**
     * @var string
     */
    private $userId;
    
    /**
     * @var PDO 
     */
    private $pdo;
     
    public function __construct(string $userId, PDO $pdo)
    {  
        $this->userId = $userId;
        $this->pdo = $pdo;
    } 

    public function amount(): string
    {
        $stmt = $this->pdo->prepare('SELECT SUM(debit - credit) FROM balances WHERE user_id = ?');
        $stmt->execute([$this->userId]);
        
        return $stmt->fetchColumn();
    }
    
    public function currency(): Currency
    {
        return new USD();
    }
    
    public function scale(): int
    {
        return 2;
    }
}

此外,您可以创建一些静态实现,如TwoDollars

<?php

declare(strict_types=1);

use ElegantBro\Money\Currencies\USD;
use ElegantBro\Money\Currency;
use ElegantBro\Money\Money;

final class TwoDollars implements Money
{
    public function amount(): string
    {
        return '2';
    }
    
    public function currency(): Currency
    {
        return new USD();
    }
    
    public function scale(): int
    {
        return 2;
    }

}

您可以在任何开箱即用或自定义操作中使用这些对象。让我们看一个例子:您需要一个税,该税为给定金额的10%,但不低于2美元。

这是一个真正的面向对象解决方案

<?php

declare(strict_types=1);

use ElegantBro\Money\ArrayLot;
use ElegantBro\Money\Money;
use ElegantBro\Money\Operations\MaxOf;
use ElegantBro\Money\Operations\Multiplied;
use ElegantBro\Money\Wrapper;

final class Tax extends Wrapper
{
    public function __construct(Money $origin, Money $minTax)
    {  
        $this->is(
            new MaxOf(
                new ArrayLot(
                    Multiplied::keepScale($origin, '0.1'),
                    $minTax
                ),
                $origin->scale()
            )
        );
    }
}

// on the client side
$tax = new Tax(
    new UserBalance($uuid, $pdo),
    new TwoDollars()
);

这是一段闪亮的声明性代码,但最大的优势是延迟。没有计算会在amount方法被调用之前发生。

使用类似moneyphp/money的过程式实现,您必须创建一些类似MoneyHelper的东西,其中包含静态的tax方法

<?php

declare(strict_types=1);

use Money/Money;

class MoneyHelper
{
    public static function tax(Money $amount, Money $minTax): Money
    {
        return Money::max(
            $amount->multiply('0.1'),
            $minTax
        );
    }
    
    // usually helpers contain dozens of static methods
}

// on the client side
$tax = MoneyHelper::tax(
    $userBalance, // you should fetch user balance from the database before
    Money::USD(200)
);

很可能这个结果在后续的计算中并不需要。例如,存在某些免税条件,您应该在计算税之前检查它,以避免不必要的数据库请求并最终导致临时耦合。

安装

$ composer require elegant-bro/money

对于贡献者

在创建pull request之前,在本地运行所有测试。

构建测试容器并运行所有测试

make all

其他命令

# build the Dockerfile
make build 

# install composer requirements
make install

# enter the container shell
make shell

# style check
make style-check

# run unit tests
make unit

# ensure coverage is 100%
make coverage