PHP RFC: 允许 __toString () 方法抛出异常
- 日期:2019-04-30
- 作者:Nikita Popov nikic@php.net
- 状态:已实现 (PHP 7.4)
- 目标版本:PHP 7.4
- 实现: https://github.com/php/php-src/pull/3887
简介
当前禁止从 __toString()
中引发异常,这将导致致命错误。这使得在 __toString()
中编写任意代码都是危险的,并使其作为通用 API 的使用成为问题。这个 RFC 消除了这个限制。
当前行为的基本原理是,在整个引擎和标准库的很多地方都进行了字符串转换,并且并非所有地方都准备“正确地”处理异常,因为从某种意义上说,应尽早处理异常。
从技术的角度来看,这种限制最终是徒劳的,因为在字符串转换期间的异常仍然可以被可恢复的错误转换为异常的错误处理程序触发:
[set_error_handler](http://www.php.net/set_error_handler)(function() {
throw new Exception();
});
try {
(string) new stdClass;
} catch (Exception $e) {
echo "(string) threw an exception...\n";
}
事实上,Symfony 使用此漏洞 可以解决当前的限制。不幸的是,这依赖于$errcontext
参数,该参数在 PHP 8中已经消失了。
尽管如此,之前这个话题的讨论并没有使这个限制松动,直到我们对该代码库的字符串转换进行全面的审核。这已经在附加的实现中完成。pull request。
提议
允许从__toString()
抛出异常,其行为将和往常一样,不再触发致命错误。
另外,按照PHP7建立的错误策略,将 ”could not be converted to string” 和 “__toString() must return a string value” 的致命错误转换为正确的Error
异常。
扩展准则
扩展作者想要确保他们优雅地处理字符串转换带来的异常,应该考虑以下的准则:
-
如果
zval_get_string()
,convert_to_string()
生成一个异常,它们仍会产生一个字符串。这个字符串保证可以被保留。这意味着不必释放它,但是可以这样做。你可以在上下文中选择更方便的选项。 -
如果一个对象转换成字符串失败,那么字符串转换的结果为空,如果数组转换成字符串并且错误处理程序将结果提升为异常,那么字符串转换的结果将为“Array”。(其行为同以前一样。)
-
使用常规的
if (EG(exception))
来检查是否抛出异常就足够了:
zend_string *str = zval_get_string(val);
if (EG(exception)) {
// 可能在这里释放其他资源。
return;
}
- 除此之外,模型转换作为一个容易犯错的操作,提供了许多帮助 API:
// 像 zval_get_string(),但是在转换失败的时候返回 NULL 。
zend_string *str = zval_try_get_string(val);
if (!str) {
// 可能在这里释放其他资源。
return;
}
// 主代码
zend_string_release(str);
// 像 zval_get_tmp_string(),但是在转换失败的时候返回 NULL 。
zend_string *tmp, *str = zval_try_get_tmp_string(val, &tmp);
if (!str) {
// 可能在这里释放其他资源。
return;
}
// 主代码
zend_tmp_string_release(tmp);
// 像 convert_to_string() ,但是在转换成功/失败的时候返回布尔值。
if (!try_convert_to_string(val)) {
// 可能在这里释放其他资源。
return;
}
// 主代码。
-
try_convert_to_string()
在转换失败的时候,不会修改初始值。因此,使用它比convert_to_string()
和异常检查更为安全。 -
虽然检查每个字符串转换会确保你的安全,但是忽视这些检查通常只会导致一些不需要的计算和可能的冗余警告。最主要的是注意修改例如数据库这种持久性结构的操作。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。