Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/251.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/design-patterns/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
PHP中的契约编程_Php_Design Patterns_Code Contracts_Design By Contract - Fatal编程技术网

PHP中的契约编程

PHP中的契约编程,php,design-patterns,code-contracts,design-by-contract,Php,Design Patterns,Code Contracts,Design By Contract,契约式编程是.NET中的一种现代趋势,但是PHP中代码契约的库/框架呢?您认为这个范例对PHP的适用性如何 谷歌搜索“php代码合同”对我来说毫无意义 注:我所说的“契约式代码”,是指与.NET或PHP接口无关。我猜维基百科提到了面向组件的软件方法。在这种方法中,方法被称为组件的公共接口或契约 合同是服务提供者和客户之间的一种“协议”。在组件环境中,系统由不同的创建者/供应商的组件组成,合同的“构建”至关重要 在这种环境中,将您的组件视为一个黑匣子,它必须能够有效地与其他人创建的其他组件共存和协

契约式编程是.NET中的一种现代趋势,但是PHP中代码契约的库/框架呢?您认为这个范例对PHP的适用性如何

谷歌搜索“php代码合同”对我来说毫无意义


注:我所说的“契约式代码”,是指与.NET或PHP接口无关。

我猜维基百科提到了面向组件的软件方法。在这种方法中,方法被称为组件的公共接口或契约

合同是服务提供者和客户之间的一种“协议”。在组件环境中,系统由不同的创建者/供应商的组件组成,合同的“构建”至关重要

在这种环境中,将您的组件视为一个黑匣子,它必须能够有效地与其他人创建的其他组件共存和协作,从而形成一个更大的系统或更大系统的子系统,等等


有关更多详细信息,我建议您在谷歌上搜索“组件软件-超越面向组件编程”一书,查找所有与面向组件编程相关的内容。

我好奇地搜索了同样的内容,发现了这个问题,因此将尝试给出答案

首先,从设计上讲,PHP并不是真正的代码契约。在需要时,您甚至无法在方法中强制执行核心类型的参数,因此我几乎不相信有一天PHP中会存在代码契约

让我们看看如果我们执行自定义的第三方库/框架实现会发生什么

1.前提条件 自由地将我们想要的一切传递给一个方法使得代码契约(或某种或多或少类似于代码契约的东西)非常有价值,至少在前提条件上是如此,因为与普通编程语言相比,保护方法不受参数中坏值的影响更加困难,其中类型可以通过语言本身强制执行

这样写会更方便:

public function AddProduct($productId, $name, $price, $isCurrentlyInStock)
{
    Contracts::Require(__FILE__, __LINE__, is_int($productId), 'The product ID must be an integer.');
    Contracts::Require(__FILE__, __LINE__, is_string($name), 'The product name must be a string.');
    Contracts::Require(__FILE__, __LINE__, is_int($price), 'The price must be an integer.');
    Contracts::Require(__FILE__, __LINE__, is_bool($isCurrentlyInStock), 'The product availability must be an boolean.');

    Contracts::Require(__FILE__, __LINE__, $productId > 0 && $productId <= 5873, 'The product ID is out of range.');
    Contracts::Require(__FILE__, __LINE__, $price > 0, 'The product price cannot be negative.');

    // Business code goes here.
}
2.后条件:大问题 先决条件容易做到的事情对于后决条件仍然是不可能的。当然,你可以想象这样的情况:

public function FindLastProduct()
{
    $lastProduct = ...

    // Business code goes here.

    Contracts::Ensure($lastProduct instanceof Product, 'The method was about to return a non-product, when an instance of a Product class was expected.');
    return $lastProduct;
}
唯一的问题是,这种方法与代码契约无关,无论是在实现级别(就像一个前置条件示例),还是在代码级别(因为后置条件在实际业务代码之前,而不是在代码和方法返回之间)

这还意味着,如果一个方法或一个
抛出中有多个返回,将永远不会检查后置条件,除非在每次
返回或
抛出之前都包含
$this->sure()
(维护噩梦!)

3.不变量:可能吗? 使用setter,可以在属性上模拟某种代码契约。但setter在PHP中的实现非常糟糕,这将导致太多问题,如果使用setter而不是字段,则自动完成将不起作用

4.实施 最后,PHP并不是代码契约的最佳候选者,而且由于它的设计太差,它可能永远不会有代码契约,除非将来在语言设计上有实质性的改变

目前,当涉及后置条件或不变量时,伪代码契约是毫无价值的。另一方面,一些伪前提条件可以很容易地用PHP编写,从而使参数检查更加优雅和简短

以下是此类实施的一个简短示例:

class ArgumentException extends Exception
{
    // Code here.
}

class CodeContracts
{
    public static function Require($file, $line, $precondition, $failureMessage)
    {
        Contracts::Require(__FILE__, __LINE__, is_string($file), 'The source file name must be a string.');
        Contracts::Require(__FILE__, __LINE__, is_int($line), 'The source file line must be an integer.');
        Contracts::Require(__FILE__, __LINE__, is_string($precondition), 'The precondition must evaluate to a boolean.');
        Contracts::Require(__FILE__, __LINE__, is_int($failureMessage), 'The failure message must be a string.');

        Contracts::Require(__FILE__, __LINE__, $file != '', 'The source file name cannot be an empty string.');
        Contracts::Require(__FILE__, __LINE__, $line >= 0, 'The source file line cannot be negative.');

        if (!$precondition)
        {
            throw new ContractException('The code contract was violated in ' . $file . ':' . $line . ': ' . $failureMessage);
        }
    }
}
当然,异常可能会被日志和继续/日志和停止方法、错误页等替换

5.结论 从预合同的实施来看,整个想法似乎毫无价值。为什么我们要费心处理那些伪代码契约,它们实际上与普通编程语言中的代码契约非常不同?它给我们带来了什么?除了这样一个事实,我们可以像使用真正的代码契约一样编写检查。没有理由仅仅因为我们能做到就这么做

为什么代码契约存在于普通语言中?原因有二:

  • 因为它们提供了一种简单的方法来强制执行在代码块开始或结束时必须匹配的条件
  • 因为当我使用使用代码契约的.NET Framework库时,我可以在IDE中轻松地知道该方法需要什么,以及该方法期望得到什么,而无需访问源代码
据我所见,在PHP中的伪代码契约实现中,第一个原因非常有限,第二个原因不存在,而且可能永远不会存在

这意味着,实际上,简单的参数检查是一个不错的选择,特别是因为PHP可以很好地处理数组。这是一个旧个人项目的复制粘贴:

class ArgumentException extends Exception
{
    private $argumentName = null;

    public function __construct($message = '', $code = 0, $argumentName = '')
    {
        if (!is_string($message)) throw new ArgumentException('Wrong parameter for ArgumentException constructor. String value expected.', 0, 'message');
        if (!is_long($code)) throw new ArgumentException('Wrong parameter for ArgumentException constructor. Integer value expected.', 0, 'code');
        if (!is_string($argumentName)) throw new ArgumentException('Wrong parameter for ArgumentException constructor. String value expected.', 0, 'argumentName');
        parent::__construct($message, $code);
        $this->argumentName = $argumentName;
    }

    public function __toString()
    {
        return 'exception \'' . get_class($this) . '\' ' . ((!$this->argumentName) ? '' : 'on argument \'' . $this->argumentName . '\' ') . 'with message \'' . parent::getMessage() . '\' in ' . parent::getFile() . ':' . parent::getLine() . '
Stack trace:
' . parent::getTraceAsString();
    }
}

class Component
{
    public static function CheckArguments($file, $line, $args)
    {
        foreach ($args as $argName => $argAttributes)
        {
            if (isset($argAttributes['type']) && (!VarTypes::MatchType($argAttributes['value'], $argAttributes['type'])))
            {
                throw new ArgumentException(String::Format('Invalid type for argument \'{0}\' in {1}:{2}. Expected type: {3}.', $argName, $file, $line, $argAttributes['type']), 0, $argName);
            }
            if (isset($argAttributes['length']))
            {
                settype($argAttributes['length'], 'integer');
                if (is_string($argAttributes['value']))
                {
                    if (strlen($argAttributes['value']) != $argAttributes['length'])
                    {
                        throw new ArgumentException(String::Format('Invalid length for argument \'{0}\' in {1}:{2}. Expected length: {3}. Current length: {4}.', $argName, $file, $line, $argAttributes['length'], strlen($argAttributes['value'])), 0, $argName);
                    }
                }
                else
                {
                    throw new ArgumentException(String::Format('Invalid attributes for argument \'{0}\' in {1}:{2}. Either remove length attribute or pass a string.', $argName, $file, $line), 0, $argName);
                }
            }
        }
    }
}
用法示例:

/// <summary>
/// Determines whether the ending of the string matches the specified string.
/// </summary>
public static function EndsWith($string, $end, $case = true)
{
    Component::CheckArguments(__FILE__, __LINE__, array(
        'string' => array('value' => $string, 'type' => VTYPE_STRING),
        'end' => array('value' => $end, 'type' => VTYPE_STRING),
        'case' => array('value' => $case, 'type' => VTYPE_BOOL)
    ));

    $stringLength = strlen($string);
    $endLength = strlen($end);
    if ($endLength > $stringLength) return false;
    if ($endLength == $stringLength && $string != $end) return false;

    return (($case) ? substr_compare($string, $end, $stringLength - $endLength) : substr_compare($string, $end, $stringLength - $endLength, $stringLength, true)) == 0;
}
//
///确定字符串的结尾是否与指定的字符串匹配。
/// 
公共静态函数EndsWith($string,$end,$case=true)
{
组件::CheckArguments(_文件,_线,数组(
'string'=>array('value'=>$string,'type'=>VTYPE\u string),
'end'=>array('value'=>$end,'type'=>VTYPE\u字符串),
'case'=>array('value'=>$case,'type'=>VTYPE\u BOOL)
));
$stringLength=strlen($string);
$endLength=strlen($end);
如果($endLength>$stringLength)返回false;
如果($endLength==$stringLength&$string!=$end)返回false;
返回($case)?substr_compare($string,$end,$stringLength-$endLength):substr_compare($string,$end,$stringLength-$endLength,$stringLength,true))==0;
}
如果我们想检查不只是依赖于参数的前提条件(例如检查前提条件中属性的值),这是不够的。但在大多数情况下,我们只需要检查参数和PHPAR中的伪代码契约
/// <summary>
/// Determines whether the ending of the string matches the specified string.
/// </summary>
public static function EndsWith($string, $end, $case = true)
{
    Component::CheckArguments(__FILE__, __LINE__, array(
        'string' => array('value' => $string, 'type' => VTYPE_STRING),
        'end' => array('value' => $end, 'type' => VTYPE_STRING),
        'case' => array('value' => $case, 'type' => VTYPE_BOOL)
    ));

    $stringLength = strlen($string);
    $endLength = strlen($end);
    if ($endLength > $stringLength) return false;
    if ($endLength == $stringLength && $string != $end) return false;

    return (($case) ? substr_compare($string, $end, $stringLength - $endLength) : substr_compare($string, $end, $stringLength - $endLength, $stringLength, true)) == 0;
}
class Model {

public function getFoos($barId, $includeBaz = false, $limit = 0, $offset = 0){

    $contract = new Contract();
    $contract->term('barId')->id()->end()
             ->term('includeBaz')->boolean()->end()
             ->term('limit')->natural()->end()
             ->term('offset')->natural()->end()
             ->metOrThrow();

    /* Continue with peace of mind ... */

}