php-deal / framework
PHP 设计-by-合同框架
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: 2024-09-15 03:13:52 UTC
README
PHP 设计-by-合同框架
什么是设计-by-合同?
类或接口的规范是该类或接口提供的非私有项的集合,以及它们的使用说明,如phpDoc所述。《设计-by-合同》是一种有效的规范创建技术。设计-by-合同 是一种在两个当事人之间传达一种正式、明确的协议的技术。
设计-by-合同的基本思想是将类或接口提供的服务视为类(或接口)与其调用者之间的合同。在这里,“合同”一词旨在传达双方之间的一种正式、明确的协议。
- 类对调用者提出的请求
- 类对调用者做出的承诺
如果调用者满足要求,则类承诺提供一些定义良好的服务。对规范/合同的某些更改会破坏调用者,而某些更改则不会。为了确定更改是否会破坏调用者,C++ 常见问题解答使用了一个难忘的短语“不要求更多,不承诺更少”:如果新规范没有比之前要求调用者更多,并且它没有承诺比之前提供更少,则新规范与旧规范兼容,不会破坏调用者。
安装
可以使用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
,则将从给定级别(类、方法)继承所有合约,而无需在当前类或方法上提供合约
注意:
- 仅当您提供此包中的任何给定注解时,才会进行合约的解析。没有它,您的合约将无法工作!
- 注解 不能 有花括号(
{}
),否则注解读取器找不到它们。
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
个字面量作为参数。
对于后置条件(确保和不变量合约),子类继承合约,它们不需要 @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 语法高亮。
常见问题
致命错误:未捕获错误:类 '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/', ));