我已经构建了一系列字符串函数,它们执行各种操作,我注意到我实际上没有任何内部函数检查,以确保变量是一个字符串,然后才能完成它。
因此,在开发过程中的某些情况下,我偶然传递了除字符串以外的其他内容,从而导致错误。
现在,我想知道这是否是我应该一直这样做的事情。首先检查以确保已发送正确类型的数据/检查可能首先出现问题的事情,以某种方式记录它们,然后如果一切正常,请使用它做一些事情。
这是我应该坚持的吗?
答案 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
可以使用一些解决方法:
描述于: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。这是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扩展程序的一部分: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()验证