lisachenko / php-deal
Requires
- php: ~7.1
- goaop/framework: ^2.3
Requires (Dev)
- beberlei/assert: ~3.0
- phpstan/phpstan: ^0.11.0
- phpunit/phpunit: ^7.5
- roave/security-advisories: dev-master
- slevomat/coding-standard: ^4.8
- squizlabs/php_codesniffer: ^3.4
- symfony/console: ~3.0|~4.0
Suggests
- beberlei/assert: Thin assertion library for input validation in business models. Used for tests.
This package is not auto-updated.
Last update: 2022-02-01 12:31:06 UTC
README
设计契约框架用于PHP
什么是设计契约?
类或接口的规范是非私有项目的集合,这些项目作为服务提供给调用者,包括使用说明,如phpDoc中所述。《设计契约》是一种创建规范的有效技术。
设计契约的基本思想是将类或接口提供的服务视为类(或接口)与其调用者之间的契约。在这里,“契约”一词旨在传达双方之间的一种正式、明确的协议。
- 类对调用者提出的要求
- 类对调用者做出的承诺
如果调用者满足要求,则类承诺提供一些明确定义的服务。对规范/契约的一些修改会破坏调用者,而另一些则不会。为了确定修改是否会破坏调用者,C++ FAQ使用了一个容易记住的短语“要求不多,承诺不少”:如果新的规范对调用者的要求不比以前多,并且它不承诺提供比以前更少的,则新的规范与旧规范兼容,不会破坏调用者。
安装
可以使用composer安装PhpDeal框架。安装很简单,只需运行以下命令让composer下载框架及其依赖项:
$ composer require php-deal/framework
配置
将以下代码放在应用程序入口点的开头或从外部文件中包含它。
$instance = ContractApplication::getInstance(); $instance->init(array( 'debug' => true, 'appDir' => __DIR__, 'excludePaths' => [ __DIR__ . '/vendor' ], 'includePaths' => [ ], 'cacheDir' => __DIR__.'/cache/', ));
Symfony配置
将以下代码放入app_dev.php中,并根据您的文件夹结构进行调整。appDir必须指向包含src文件的文件夹,而不是根目录文件夹!
$instance = ContractApplication::getInstance(); $instance->init(array( 'debug' => true, 'appDir' => __DIR__ . '/../src', 'excludePaths' => [ ], 'includePaths' => [ ], 'cacheDir' => __DIR__.'/var/cache/', ));
前置和后置契约
前置契约指定在执行语句之前必须满足的先决条件(要求)。最典型的用途是在验证函数参数时。后置契约(承诺)验证语句的结果。最典型的用途是在验证方法返回值及其任何副作用时。语法是
<?php namespace Vendor\Namespace; use PhpDeal\Annotation as Contract; //import DbC annotations /** * Some account class */ class Account { /** * Current balance * * @var float */ protected $balance = 0.0; /** * Deposits fixed amount of money to the account * * @param float $amount * * @Contract\Verify("$amount>0 && is_numeric($amount)") * @Contract\Ensure("$this->balance == $__old->balance+$amount") */ public function deposit($amount) { $this->balance += $amount; } }
根据定义,如果前置契约失败,则接收到的参数是错误的。抛出ContractViolation异常。如果后置契约失败,则表示主体存在错误。抛出ContractViolation异常。
不变量
不变量用于指定类必须始终为真的特征(除非在执行受保护的或私有成员函数时)。
不变量是一个契约,说明断言必须为真。在类构造函数完成后以及在类公共方法结束时检查不变量。
<?php namespace Vendor\Namespace; use PhpDeal\Annotation as Contract; //import DbC annotations /** * Some account class * * @Contract\Invariant("$this->balance > 0") */ class Account { /** * Current balance * * @var float */ protected $balance = 0.0; /** * Deposits fixed amount of money to the account * * @param float $amount */ public function deposit($amount) { $this->balance += $amount; } }
不变量包含断言表达式,因此当它们失败时,会抛出ContractViolation异常。
注意:不变量中的代码不能直接或间接调用类的任何公共非静态成员。这样做会导致栈溢出,因为不变量将以无限递归的方式被调用。
合同传播
合同在继承中存在一些差异
- 确保
- 如果提供了
Ensure
,将自动从父类或接口继承所有合同
- 验证
- 如果提供了
Verify
,将不会从父类或接口继承合同 - 要继承合同,您需要提供
@inheritdoc
或Inherit
合同
- 不变量
- 如果提供了
Invariant
,将自动从父类或接口继承所有合同
- Inherit
- 如果提供了
Inherit
,将自动从给定级别(类、方法)继承所有合同,无需在当前类或方法上提供合同
注意:
- 只有当您提供该包中的任何给定注释时,才会解析合同。如果没有它,您的合同将无法工作!
- 注释不得有花括号(
{}
),否则注释读取器无法找到它们。
class Foo extends FooParent { /** * @param int $amount * @Contract\Verify("$amount != 1") */ public function bar($amount) { ... } } class FooParent { /** * @param int $amount * @Contract\Verify("$amount != 2") */ public function bar($amount) { ... } }
Foo::bar
接受2
个字面量参数,不接受1
。
使用@inheritdoc
class Foo extends FooParent { /** * @param int $amount * @Contract\Verify("$amount != 1") * {@inheritdoc} */ public function bar($amount) { ... } } class FooParent { /** * @param int $amount * @Contract\Verify("$amount != 2") */ public function bar($amount) { ... } }
Foo::bar
不接受1
和2
个字面量参数。
对于后置条件(Ensure和Invariant合同),子类继承合同,它们不需要@inheritdoc
。示例
/** * @Contract\Invariant("$this->amount != 1") */ class Foo extends FooParent { } /** * @Contract\Invariant("$this->amount != 2") */ class FooParent { /** * @var int */ protected $amount; /** * @param int $amount */ protected function setBar($amount) { $this->amount = $amount; } }
Foo::setBar
不接受1
和2
个字面量参数。
如果您不希望在当前方法/类上提供合同,可以使用Inherit
注释
class Foo extends FooParent { /** * @param int $amount * @Contract\Inherit */ public function bar($amount) { ... } } class FooParent { /** * @param int $amount * @Contract\Verify("$amount != 2") */ public function bar($amount) { ... } }
Foo:bar()
接受除2
之外的所有内容
与断言库集成
为了增强合同的功能,可以使用断言库。
/** * Deposits fixed amount of money to the account * * @param float $amount * * @Contract\Ensure("Assert\Assertion::integer($this->balance)") */ public function deposit($amount) { $this->balance += $amount; }
IDE集成
为了提高您的PhpStorm生产力,您应该安装一个Go! AOP Framework插件(>=1.0.1),以实现定义合同和导航到AOP建议的PHP语法高亮显示。
常见问题
致命错误:未捕获的Error: Class 'Go\ParserReflection\Instrument\PathResolver'
Fatal error: Uncaught Error: Class 'Go\ParserReflection\Instrument\PathResolver' not found in .../vendor/goaop/parser-reflection/src/ReflectionEngine.php on line XXX
如果您的appDir
配置指向与您的vendor
目录相同的级别,则会出现这种情况。要解决此问题,请尝试将您的vendor
文件夹添加到excludePaths
配置中。
ContractApplication::getInstance()->init(array( 'debug' => true, 'appDir' => __DIR__,, 'excludePaths' => [ __DIR__ . '/vendor' ], 'cacheDir' => __DIR__.'/cache/', ));