php-deal/framework

PHP 设计-by-合同框架

1.0.0 2019-01-27 09:12 UTC

README

PHP 设计-by-合同框架

什么是设计-by-合同?

类或接口的规范是该类或接口提供的非私有项的集合,以及它们的使用说明,如phpDoc所述。《设计-by-合同》是一种有效的规范创建技术。设计-by-合同 是一种在两个当事人之间传达一种正式、明确的协议的技术。

设计-by-合同的基本思想是将类或接口提供的服务视为类(或接口)与其调用者之间的合同。在这里,“合同”一词旨在传达双方之间的一种正式、明确的协议。

  • 类对调用者提出的请求
  • 类对调用者做出的承诺

如果调用者满足要求,则类承诺提供一些定义良好的服务。对规范/合同的某些更改会破坏调用者,而某些更改则不会。为了确定更改是否会破坏调用者,C++ 常见问题解答使用了一个难忘的短语“不要求更多,不承诺更少”:如果新规范没有比之前要求调用者更多,并且它没有承诺比之前提供更少,则新规范与旧规范兼容,不会破坏调用者。

Build Status GitHub release Code Coverage Scrutinizer Code Quality Minimum PHP Version License

安装

可以使用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异常。

注意:不变性中的代码不得直接或间接调用类的任何公共非静态成员。这样做会导致栈溢出,因为不变性将以无限递归的方式被调用。

合同传播

合同的继承有一些差异

  1. 确保
  • 如果提供 Ensure,它将自动继承父类或接口的所有合同
  1. 验证
  • 如果提供了 Verify,则将不会从父类或接口继承合约
  • 要继承合约,您需要提供 @inheritdocInherit 合约
  1. 不变量
  • 如果提供了 Invariant,则将从父类或接口继承所有合约
  1. 继承
  • 如果提供了 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 不接受 12 个字面量作为参数。

对于后置条件(确保和不变量合约),子类继承合约,它们不需要 @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 不接受 12 个字面量作为参数。

如果您不想在当前方法/类上提供合约,可以使用 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 语法高亮。 PhpStorm example

常见问题

致命错误:未捕获错误:类 '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/',
));