可选的PHP类型提示/检查单元测试或静态分析


Optional PHP type hint/check for unit testing or static analysis?

PHP类型提示不支持标量变量[1],如int或string

然而,我们发现在持续集成过程中,在函数中注释类型(int或string)仍然非常有用,可以发现bug,例如

目前我使用的方法是

function foo($s) {
    //assert( is_string($s), 'not a string' );
    ...
}

在单元测试和开发模式中,断言将被取消注释以发现潜在的错误。

我正在寻找是否有更好的方法。

[1] http://www.php.net/manual/en/language.oop5.typehinting.php

AOP是一个有趣而优雅的解决方案。您可以从代码中删除所有断言,并开始使用标准的phpdocs,如下所示:

/**
* @param string $s
*/
function foo($s) {
    ...
}

然后在单元测试和开发模式中使用AOP框架,就像这样:http://go.aopphp.com/blog/2013/02/11/aspect-oriented-framework-for-php/

来自他们的文档:

…在10-20行代码的帮助下,我们可以拦截所有的的所有类中的公共、受保护和静态方法应用程序…

您将使用它来拦截所有方法,读取参数,获取ReflectionMethod对象,解析与类型相关的注释并执行运行时检查。这听起来很复杂,但其实很容易做到。

结果:它将在测试期间消耗一些运行时资源,每个包含的PHP文件(不多),但它将看起来更好(更干净),为您的代码库

如果您不介意在应用程序中增加一点开销,可以考虑在文件"typecheck.php"中定义一组类型检查函数:

   assert_string($s) {  
      assert( is_string($), 'not a string');
   }
   assert_int($i) { 
     assert( is_int($i), 'not an integer);
   }
   ... lots more type checks as appropriate ...

"require"这个"typecheck.php"在你所有的脚本的顶部,并写一些类似于你的例子来做类型检查:

    require("typecheck.php");
    ...
    function foo($s) {
       assert_string($s); 
    ...

那么您不需要注释签入和签出。这个加了nice属性,编写的代码包含断言,作为文档,以帮助代码维护者;它们的存在将确保在他更改代码时检查它们,并将提醒他在需要时添加更多。

您可以通过这种方式添加各种方便的、专门的检查;考虑:

     assert_integer_range($i, $l, $u) {
         assert_int($i);
         assert($i>=$l);
         assert($i<=$u);
    }

     bar($n) {
         assert_integer_range($n,1,10);
     ...

您可能获得的任何像样的静态分析工具都将从断言的存在中获益良多。

如果assert_xxx调用的开销太大,您无法在生产代码中承受,那么您可以降低生产代码的成本。在产品代码中使用另一个"typecheck.php"文件,该文件定义了相同的函数,但不进行检查。不完美,但会有帮助。

这个解决方案不需要任何工具,除了每个程序员已经拥有的文本编辑器。

我使用了一个组合的解决方案,类似于下面的答案。

像这样使用标准docblock:

/**
 * This function sets InternalVariable to be $s
 * @param string $s
 */
function foo($s) 
{
    ...
}

现在,我还结合PHP_CodeSniffer来确保启用docblock,以确保注释已经放置到位并定义。

然后将PHPUnit与assert一起使用,以测试该函数是否仍然使用字符串。
class FOO_TEST
{
    public function test_foo()
    {
        $TestObject = new FOO_CLASS();
        $TestObject->Foo(1);
        $this->assertTrue( is_string($TestObject->InternalString, 'Foo should have accepted a string') );
    }
}

作为规则,如果传递给函数的变量必须是某种类型,那么您总是可以在函数本身中强制执行。

如果您使用的是较新的PHP,那么请考虑使用标准PHP库sl - types

/**
 * This function sets InternalVariable to be $s
 * @param SplString $s
 */
function foo(SplString $s) 
{
    ...
}