为啥不可能从 __toString() 抛出异常?
Posted
技术标签:
【中文标题】为啥不可能从 __toString() 抛出异常?【英文标题】:Why it's impossible to throw exception from __toString()?为什么不可能从 __toString() 抛出异常? 【发布时间】:2011-01-26 15:26:22 【问题描述】:为什么不能从 __toString() 抛出异常?
class a
public function __toString()
throw new Exception();
$a = new a();
echo $a;
上面的代码产生了这个:
Fatal error: Method a::__toString() must not throw an exception in /var/www/localhost/htdocs/index.php on line 12
我被指向http://php.net/manual/en/migration52.incompatible.php 描述了这种行为,但为什么呢?有什么理由这样做吗?
这里有人知道吗?
在错误跟踪器 php-dev-team 中,除了查看手册之外什么也没说:http://bugs.php.net/50699
【问题讨论】:
PHP 7.4 现在 allows exceptions 在__toString()
【参考方案1】:
似乎从 php 7.4 开始,从 __toString() 抛出异常是允许的。我进行了 php7.2 兼容性检查,它这么说并指出了 Doctrine StaticReflectionClass 和 StaticReflectionProperty。
请找到更多关于提案的信息https://wiki.php.net/rfc/tostring_exceptions
【讨论】:
【参考方案2】:经过几次搜索,我发现了这个,上面写着:
Johannes 解释说,没有办法确保 Zend 引擎正确处理强制转换为字符串期间引发的异常,并且除非引擎的大部分内容,否则这不会改变被重写。他补充说,过去曾就此类问题进行过讨论,并建议 Guilherme 检查档案。
上面引用的Johannes 是 PHP 5.3 发布管理器,因此它可能是您可能会发现的关于 PHP 为何会以这种方式运行的“官方”解释。
该部分继续提到:
__toString()
会奇怪地接受trigger_error()。
因此,在__toString()
中的错误报告方面并非全部丢失。
【讨论】:
嘿,谢谢。但是 trigger_error() 不能仅仅因为它是全局的并且 try/catch 是具体的而替换 try/catch。 @zerkms - 这是真的,它不能替代。也许如果有足够多的人表达他们的意见,他们会重写 Zend 引擎。 :) 此外,许多框架会捕获错误并将其作为异常重新抛出 - 这会带来完全相同的问题。 这值得大扫除 其实,如果你显式调用了__toString(),那么echo $a->__toString();就会成功的将异常传递为致命错误,其中echo $ a; 不会。【参考方案3】:作为对接受的答案的回应,我想出了一个(也许)更好的方法来处理 __toString()
内部的异常:
public function __toString()
try
// ... do some stuff
// and try to return a string
$string = $this->doSomeStuff();
if (!is_string($string))
// we must throw an exception manually here because if $value
// is not a string, PHP will trigger an error right after the
// return statement, thus escaping our try/catch.
throw new \LogicException(__CLASS__ . "__toString() must return a string");
return $string;
catch (\Exception $exception)
$previousHandler = set_exception_handler(function ()
);
restore_error_handler();
call_user_func($previousHandler, $exception);
die;
这假设定义了一个异常处理程序,这是大多数框架的情况。与 trigger_error
方法一样,这样做会违背 try..catch 的目的,但它仍然比使用 echo
转储输出要好得多。此外,许多框架将错误转换为异常,因此trigger_error
无论如何都不起作用。
作为额外的奖励,您将获得完整的堆栈跟踪,与正常异常和您选择的框架的正常开发生产行为一样。
在 Laravel 中运行良好,我很确定它可以在几乎所有现代 PHP 框架中运行。
屏幕截图相关:注意:在此示例中,output()
由 __toString()
方法调用。
【讨论】:
【参考方案4】:我找到了简单的解决方案:
当发生错误转换为字符串时,只需在 __toString 中返回类似非字符串类型的内容:NULL、FALSE 甚至 Exception。
这将导致这样的输出(在 php -a 交互式 SHELL 中):
Catchable fatal error: Method MyClass::__toString() must return a string value in php shell code on line 1
【讨论】:
它对我有用:当 __toString 中的转换出现致命错误时是不需要的。然后可以处理可捕获的致命错误,而不是只在 __toString 中获得致命错误 我不确定您是否已阅读该问题。只是提醒您:我问为什么不可能这样做。【参考方案5】:我的猜测是__toString
是hackish,因此存在于典型堆栈之外。那么,抛出的异常不知道该去哪里。
【讨论】:
尤其是echo $a->__toString()
可以抛出异常,而echo $a
不能。
__toString 不是hackish,它是提供对象的字符串表示的完全标准方式。 (string)$a 和 $a->__toString() 之间的区别在于引擎处理调用的方式,这与 $x->something 和 $x->__call("something") 之间的区别相同.一种是直接调用对象中的函数(恰好以__开头的函数),另一种是Zend引擎内部处理的魔术方法。
@Sprog 我认为 hackish 术语正在被应用,它的实现是 hackish。【参考方案6】:
我认为这个决定的理由从未被公开过。看起来像一些内部架构限制。
在更抽象的层面上,这是有道理的。对象应该能够返回其自身的字符串表示,这种操作没有失败的理由。
【讨论】:
"一个对象应该能够返回一个它自己的字符串表示,这种操作没有理由失败。"呵呵 :-) echo $a->__toString();抛出它;-) 这是意料之中的。在这种情况下,方法也应该能够返回转换为字符串的东西,但是不,我们得到未处理的异常。 你得到的异常不是从__toString()
内部抛出的
但我的意思是“一个对象应该能够返回其自身的字符串表示”对于“一个方法应该能够返回一些东西来回显”也应该是正确的,正如你提到的。但这是错误的。 ps:我得到的异常是从 __toString()
class a public function __toString() throw new Exception(); $a = 新的 a();回声 $a->__toString();嗯?当对象不存在时,这是一个致命错误(并注意),而不是异常
@zerkms 这正是因为直接调用它很好,它存在于正常调用过程的范围内,而 toString 可以在其他正在处理的事情的中间调用(任何时候你需要一个字符串和传递了一个对象)和处理异常需要大量工作,以确保可以在引擎中的任何位置处理异常,而不是像现在这样在方法调用边界上处理。以上是关于为啥不可能从 __toString() 抛出异常?的主要内容,如果未能解决你的问题,请参考以下文章