PHP应该首先检查所有函数的参数类型吗?

时间:2013-11-20 14:38:47

标签: php function type-hinting

我已经构建了一系列字符串函数,它们执行各种操作,我注意到我实际上没有任何内部函数检查,以确保变量是一个字符串,然后才能完成它。

因此,在开发过程中的某些情况下,我偶然传递了除字符串以外的其他内容,从而导致错误。

现在,我想知道这是否是我应该一直这样做的事情。首先检查以确保已发送正确类型的数据/检查可能首先出现问题的事情,以某种方式记录它们,然后如果一切正常,请使用它做一些事情。

这是我应该坚持的吗?

6 个答案:

答案 0 :(得分:4)

你可以看到这是一个有点争议的话题。这是我的看法:

类型提示

尽可能使用类型提示。对于原始类型,PHP中不能使用类型提示,所以是的,您应该检查以确保您已收到有效参数。如果还没有,您的函数可以抛出异常或返回一些默认值,如null或false。

防御性编程

编写可测试代码的想法是失败不是沉默或神秘的。没有理由避免显式参数验证:冗长,代码更清晰,更实用。

在验证参数之上,您可以实现错误处理程序来捕获边缘情况。但是你应该验证大多数参数,特别是如果它们对持久性数据(如数据库)有影响。

墨菲定律完全有效,因此你必须尽可能多地应对可预测的错误。无效的参数是一个容易预测的错误 - 无法验证它是代码中的时间炸弹。例如,调用is_string很容易并且扩散炸弹。

<强>拳击

另一个考虑因素是“装箱”你的变量。这导致了非常详细的代码,但它确实具有允许基元类型提示的优势。

我从来没有见过任何人通过他们的整个代码库实际做到这一点,但它就在那里。有一些SPL类可用于主要类型,所以你最终会这样:

function stringThing (\SplString $myString) { ... }

stringThing(new \SplString('This is my string'));

SplTypes强制执行基本类型,并在滥用时抛出异常。来自文档:

$string = new SplString("Testing");
try {
    $string = array(); // <----------this will throw an exception
} catch (UnexpectedValueException $uve) {
    echo $uve->getMessage() . PHP_EOL;
}

SplTypes是PECL扩展,并不总是标准PHP安装的一部分,因此请在使用之前检查扩展。该扩展也被认为是实验性的,尽管它已经存在了一段时间。

你也可以简单地创建自己的盒子:

class myStringBox {
  private $string = '';
  public function __construct($string=null) {
      if ($string)
          $this->set($string);
  }
  public function set($val) {
    if (!is_string($string)) throw new \InvalidArgumentException();
    $this->string= $val;
  }
  public function __toString() {
    return $this->string;
  }
  public function trim() { return trim($this->string); } // extend with functions?
}

...但这有一个主要的功能差异,因为你不能直接设置这样的新字符串值:

$stringBox = new myStringBox('hello world! ');
echo $stringBox; // "hello world![space]"
echo $stringBox->trim(); // "hello world!"

$stringBox = 'A new string';
echo $stringBox->trim(); // Error: Call to a member function trim() on a non-object 

相反,您必须使用setter方法:

$stringBox = new myStringBox('hello world! ');
echo $stringBox; // "hello world![space]"
echo $stringBox->trim(); // "hello world!"

$stringBox->set('A new world');
echo $stringBox->trim(); // "A new world"

这一切都引导我们回到类型提示,这可能是不必验证你的论点的最有效方法。

相关阅读

答案 1 :(得分:3)

目前,由于PHP变量的动态特性,PHP没有标量的类型提示,这是was planned for PHP 5.4 but was removed

PHP RFC请求现已打开,它是PHP 6的计划功能 https://wiki.php.net/rfc/scalar_type_hinting_with_cast

可以使用一些解决方法:

set_error_handler方法:

描述于:http://edorian.github.io/2010-03-30-typehints-hack-for-literal-values-in-php/

function typehint($level, $message) {
    if($level == E_RECOVERABLE_ERROR && preg_match(
        '/^Argument (\d)+ passed to (?:(\w+)::)?(\w+)\(\) must be an instance of (\w+), (\w+) given/', $message, $match)
    ) { 
        if($match[4] == $match[5]) {
            return true;
        }
    } 
    return false;
}
set_error_handler("typehint");

PHPTypeSafe方法

另一种解决方法是PHPTypeSafe。这是5.3的旧库,但是PHP执行明显减慢:

class Foo
{
    public static function bar(string $msg, int $counter)
    {
        echo "$counter. run: $msg\n\n";
    }
}

$postCount = 0;

Foo::bar('test', ++$postCount);

echo "The next run will result in an an ErrorException: \n\n";

Foo::bar(false, ++$postCount);

您可以在以下位置阅读完整示例: https://github.com/max-horvath/PHPTypeSafe/blob/master/src/examples/php/com/maxhorvath/phptypesafe/SimpleExample.php


SPL类型方法

这是最标准和推荐的方法,问题是必须用对象包装变量。这是SPL扩展程序的一部分:http://www.php.net/manual/en/book.spl-types.php

这是一个小例子:

function myFunction(SplInt $int) {
    //foo
}

$int = new SplInt(94);
myFunction($int);

您可以在Chris answer

找到有关SPL类型的更多信息

为了完整起见:前一段时间,作为最好的PHP贡献者之一的Sara Golemon女士试图为类似于SPL类型的标量变量进行自动装箱。这是一个非常阿尔法状态的小扩展:

https://github.com/sgolemon/objectifier

问候

答案 2 :(得分:2)

在标量变量的真实类型提示缺失的情况下,您可以使用doc-comments。如果你使用一个像样的IDE,你将从IDE获得合理的类型警告;即使PHP本身没有强制执行它们,这通常足以阻止你制造任何咆哮者。

例如:

/**
 * @param int custID
 */
function loadCustomer($custID) {
    ....
}

鉴于上述情况,一个体面的IDE将为您提供参数的代码完成/类型提示,就像它被约束为整数一样。它不会完全阻止你错误地传递一个字符串,但如果它知道你传递了错误的类型,它会警告你。

答案 3 :(得分:0)

不,除了边缘情况,你没有。

我建议您使用http://php.net/manual/en/class.errorexception.php而不是默认的PHP错误处理程序。 ErrorException即使发出通知也会停止脚本执行。

function exception_error_handler($errno, $errstr, $errfile, $errline ) {
    throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
}
set_error_handler("exception_error_handler");

我更喜欢这种方法,因为它允许长期捕获所有未经审查的案例,并编写适当的条件来处理它们。

答案 4 :(得分:0)

我建议接受函数中的所有用户输入,并将参数转换为字符串(以确保),否则会发生错误。例如,没有理由不使用int调用其中一个函数并使用其字符串表示对其进行处理。

function foo($bar) {
  $str = (string)$bar;
  return strlen($str);
}

这甚至适用于实现__toString()方法的类。

答案 5 :(得分:-3)

对函数参数使用is_string()验证