为啥不可能从 __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() 抛出异常?的主要内容,如果未能解决你的问题,请参考以下文章

致命错误:方法 class@anonymous::__toString() 在作曲家安装后不得抛出异常

为啥尝试更改 _arr[3] 时没有抛出异常?

为啥某些带有双反斜杠的路径抛出找不到路径异常的一部分?

为啥我应该在抛出异常指针时使用按引用捕获

wk_10.md

Java_异常