代码之家  ›  专栏  ›  技术社区  ›  zerkms

PHP中的契约编程

  •  12
  • zerkms  · 技术社区  · 15 年前

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

    谷歌搜索“代码合同php”给我什么也没有。

    Design by contract ,因此它与.NET或PHP接口无关。

    4 回复  |  直到 14 年前
        1
  •  25
  •   Arseni Mourzenko    14 年前

    我好奇地寻找着同样的东西,发现了这个问题,所以威尔试图给出一个答案。

    首先,从设计上讲,PHP并不是真正的代码契约。在需要的时候,您甚至不能在方法中强制使用核心类型的参数,所以我很难相信有一天PHP中会存在代码契约。

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

    一。先决条件

    更方便的做法是:

    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.
    }
    

    而不是:

    public function AddProduct($productId, $name, $price, $isCurrentlyInStock)
    {
        if (!is_int($productId))
        {
            throw new ArgumentException(__FILE__, __LINE__, 'The product ID must be an integer.');
        }
    
        if (!is_int($name))
        {
            throw new ArgumentException(__FILE__, __LINE__, 'The product name must be a string.');
        }
    
        // Continue with four other checks.
    
        // Business code goes here.
    }
    

    对于后条件来说,先决条件很容易做到的事情仍然是不可能的。当然,你可以想象如下:

    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;
    }
    

    唯一的问题是,这种方法与代码契约无关,无论是在实现级别(就像一个前提条件示例),还是在代码级别(因为后条件在实际业务代码之前,而不是在代码和方法返回之间)。

    它还意味着如果一个方法或 throw ,除非包含 $this->Ensure() return (维护噩梦!)。

    三。不变量:可能吗?

    四。实施

    最后,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框架库时,我可以在IDE中很容易地知道方法所需的内容,以及方法所期望的内容,而无需访问源代码。

    这意味着实际上,简单的参数检查是一个很好的选择,特别是因为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;
    }
    

    如果我们要检查不只是依赖于参数的前置条件(例如检查前置条件中属性的值),这是不够的。但在大多数情况下,我们只需要检查参数,而PHP中的伪代码契约并不是最好的方法。

    换言之,如果您的唯一目的是检查参数,那么伪代码契约就是一种过激行为。当您需要更多的东西时,它们可能是可能的,比如依赖于对象属性的前提条件。但在最后一种情况下,可能有更灵活的方法来做事情,所以使用代码契约的唯一原因是: .


    我们可以指定参数必须是类的实例。奇怪的是,没有办法指定参数必须是整数或字符串。

    所谓伪代码契约,我的意思是上述实现与.NET框架中代码契约的实现有很大不同。真正的实现只有通过改变语言本身才能实现。

    如果构建了协定引用程序集,或者更好的是,如果在XML文件中指定了协定。

    if - throw 可以做到。

        2
  •  2
  •   axiom82 Joeytje50    12 年前

    我已经创建了PHP合同,

    PHP的C#契约的轻量级多功能实现。 这些契约在许多方面都超过了C#中的功能。拜托

    https://github.com/axiom82/PHP-Contract


    下面是一个基本示例:

    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 ... */
    
    }
    

    对于文件, please visit the wiki.

        3
  •  1
  •   Alireza Rahmani Khalili    7 年前

    一个 interface is not a contact Laravel definition 是错误的),合同设计(DbC)是一种软件正确性方法。它使用前置条件和后置条件来记录(或以编程方式断言)由程序片段引起的状态更改。我找到了一个好的php方法 here

        4
  •  0
  •   Andreas    15 年前

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

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

    关于更多的细节,我建议你在google上搜索“组件软件-超越组件导向编程”一书,搜索所有与组件导向编程相关的东西。

    推荐文章