giritli/contract

此包最新版本(v0.1.2)没有提供许可证信息。

易于使用的合约类。

v0.1.2 2015-07-12 02:39 UTC

This package is auto-updated.

Last update: 2024-09-21 20:10:37 UTC


README

Build Status Scrutinizer Code Quality Code Coverage

合约

合约是PHP实现的合约模式。它允许您围绕现有类包裹一个合约,拦截结果和类状态的要求和结果。如果违反合约,则合约类还会回滚状态。

安装

要使用composer安装此包,请运行以下命令

composer require giritli/contract

示例用法

创建一个将被合约包裹的初始类。

// Create your normal class
class Account {
    public $balance = 0;
    public function deposit($amount) {
        $this->balance += (int) $amount;
    }
    public function getBalance() {
        return $this->balance;
    }
}

要使一个类成为合约,它必须扩展要包裹的类,并实现\Giritli\Contract\Contract。接口的实现包含为特质的\Giritli\Contract\ContractTrait

要在方法上创建合约,定义一个存在于父类中的方法。使用接口可以使这更容易。合约接口提供了3个方法

  • requires(\Closure $callback, $message = null)
    • 传递给此方法的任何闭包都将传递所有方法参数。它应该与父方法具有相同的参数定义。
    • 闭包必须返回true或false,如果参数上的合约通过。
    • 消息是在合约失败时抛出的异常消息。
    • 这可以用于参数验证的良好用途。
  • ensures(\Closure $callback, $message = null)
    • 传递给此方法的任何闭包都将具有初始父方法的返回值。闭包还绑定到对象,以便可以使用$this变量检查对象的状态。
    • 消息是在合约失败时抛出的异常消息。
  • enforce()
    • 此方法指定父方法何时被调用。必须在调用enforce()之前定义所有合约。

示例用法

// Create the contract class
class AccountContract extends Account implements \Giritli\Contract\Contract {
    use \Giritli\Contract\ContractTrait;
    public function deposit($amount) {
    
        $this->requires(function($amount) {
            return is_numeric($amount) && $amount >= 0;
        }, 'Amount must be a number and non negative.');

        $this->ensures(function($result) {
            return $this->balance !== 5;
        }, 'Account cannot have a balance of 5.');
    
        return $this->enforce();
    }
}

创建您的类和合约后,您可以使用它就像一个常规类一样

$account = new AccountContract();

try {
    $account->deposit(4);
} catch (\Giritli\Contract\Exception\ContractFailedException $e) {
}

$account->getBalance(); // 4

/**
 * AccountContract is always instance of Account,
 * conforms to Liskov substitution principle
 */
$account instanceof Account; // true

如果合约失败,对象将回滚到最后一个良好状态

$account = new AccountContract();

try {
    $account->deposit(3);
    $account->deposit(2); // Will fail ensures contract as balance is now 5
} catch (\Giritli\Contract\Exception\EnsureContractFailedException $e) {
    echo $e->getMessage(); // Account cannot have a balance of 5.
}

$account->getBalance(); // 3

它如何工作?

合约特质欺骗性地简单。当您使用require()ensure()enforce()定义合约方法时,它会跟踪调用堆栈以获取传递给初始方法(旨在传递给父方法)的参数。然后将这些参数传递给require合约并执行。如果任何合约失败,将抛出异常,方法不会执行。

执行完require合约后,该类然后克隆自己,并在克隆上调用enforce方法。此方法运行父方法然后运行ensure合约。如果任何ensure合约失败,将抛出异常并退出方法。如果没有抛出异常,对克隆对象的调用完成并继续到非克隆的ensure方法,然后将其应用于活动对象的所有更改。