如何解决 PHP 中缺少 finally 块的问题?

Posted

技术标签:

【中文标题】如何解决 PHP 中缺少 finally 块的问题?【英文标题】:How can I get around the lack of a finally block in PHP? 【发布时间】:2010-10-29 22:59:59 【问题描述】:

5.5 版之前的 php 没有 finally 块 - 即,在大多数合理的语言中,您可以这样做:

try 
   //do something
 catch(Exception ex) 
   //handle an error
 finally 
   //clean up after yourself

PHP 没有 finally 块的概念。

任何人都有解决语言中这个相当烦人的漏洞的经验吗?

【问题讨论】:

finally 已获得批准,应该在 PHP 5.5 中可用。 wiki.php.net/rfc/finally 好消息,我可能会在 PHP 5.5 发布后更新问题以表明 finally 块的可用性。 【参考方案1】:

解决方案,不。烦人的繁琐解决方法,是的:

$stored_exc = null;
try 
    // Do stuff
 catch (Exception $exc) 
    $stored_exc = $exc;
    // Handle an error

// "Finally" here, clean up after yourself
if ($stored_exc) 
    throw($stored_exc);

很糟糕,但应该可以。

请注意:PHP 5.5 finally(咳咳,抱歉)添加了 finally 块:https://wiki.php.net/rfc/finally(而且只用了几年...在 5.5 RC 中可用将近四年自我发布此答案以来的日期...)

【讨论】:

所以你的意思是 PHP 终于有了……它的拼写是“if” 如果我在 try 内返回会发生什么?你的if 不会执行。 这很简单,不要在 try 内返回。如果您没有注意到,我在第一句话中特别指出,这不是一个解决方案,而是一个烦人、繁琐的解决方法。由于提供 finally 保证需要解释器支持,因此无法完全解决缺少 finally 块的问题。 我承认我不是专家级的 try/catch/finally 用户,但为什么不直接在 catch 块中抛出异常呢?是因为您可能会在 throw() 之前运行一些其他代码作为清理代码吗?而且,如果(如 Rob 所说)你有一个“返回 X”;在 try 块中,其他语言在返回之前是否仍会运行 finally 块(如果是这样,那很好,尽管当我看到 return 语句时,我假设在那之后没有其他东西运行?不过对我来说非常有趣。 @OneNerd:是的,即使从 catch 块内部返回,其他语言也会运行 finally 块。即使您将其扔进 catch 块内,它们也会运行它。如果您的程序转储代码、被杀死、操作系统崩溃、断电等,它们显然不会运行它【参考方案2】:

由于这是一种语言结构,因此您不会找到简单的解决方案。 您可以编写一个函数并将其作为 try 块的最后一行和最后一行调用,然后在 try 块中重新抛出异常。

好书反对将 finally 块用于除释放资源之外的任何其他操作,因为您无法确定如果发生令人讨厌的事情时它会执行。将其称为一个令人讨厌的漏洞是一种夸大其词的说法。 相信我,很多非常好的代码都是用没有 finally 块的语言编写的。 :)

finally的意义在于不管try块成功与否都执行。

【讨论】:

【参考方案3】:

RAII 成语为finally 块提供代码级替代。创建一个包含可调用对象的类。在析构函数中,调用可调用对象。

class Finally 
    # could instead hold a single block
    public $blocks = array();

    function __construct($block) 
        if (is_callable($block)) 
            $this->blocks = func_get_args();
         elseif (is_array($block)) 
            $this->blocks = $block;
         else 
            # TODO: handle type error
        
    

    function __destruct() 
        foreach ($this->blocks as $block) 
            if (is_callable($block)) 
                call_user_func($block);
             else 
                # TODO: handle type error.
            
        
    

协调

请注意,PHP 没有变量的块作用域,所以Finally 在函数退出或(在全局作用域内)关闭序列之前不会启动。例如:

try 
    echo "Creating global Finally.\n";
    $finally = new Finally(function () 
        echo "Global Finally finally run.\n";
    );
    throw new Exception;
 catch (Exception $exc) 

class Foo 
    function useTry() 
        try 
            $finally = new Finally(function () 
                echo "Finally for method run.\n"; 
            );
            throw new Exception;
         catch (Exception $exc) 
        echo __METHOD__, " done.\n";
    


$foo = new Foo;
$foo->useTry();

echo "A whole bunch more work done by the script.\n";

将导致输出:

最后创建全局。 Foo::use 尝试完成。 最后用于方法运行。 脚本完成了一大堆工作。 Global 终于运行了。

$这个

PHP 5.3 闭包无法访问$this(在 5.4 中已修复),因此您需要一个额外的变量来访问某些 finally 块中的实例成员。

class Foo 
    function useThis() 
        $self = $this;
        $finally = new Finally(
            # if $self is used by reference, it can be set after creating the closure
            function () use ($self) 
               $self->frob();
            ,
            # $this not used in a closure, so no need for $self
            array($this, 'wibble')
        );
        /*...*/
    

    function frob() /*...*/
    function wibble() /*...*/

私有和受保护字段

可以说,PHP 5.3 中这种方法的最大问题是 finally-closure 无法访问对象的私有和受保护字段。就像访问$this 一样,这个问题在 PHP 5.4 中得到了解决。目前,private and protected properties 可以通过引用访问,正如 Artefacto 在他的 answer 中针对本网站其他地方的这个主题的问题中所展示的那样。

class Foo 
    private $_property='valid';

    public function method() 
        $this->_property = 'invalid';
        $_property =& $this->_property;
        $finally = new Finally(function () use (&$_property) 
                $_property = 'valid';
        );
        /* ... */
    
    public function reportState() 
        return $this->_property;
    

$f = new Foo;
$f->method();
echo $f->reportState(), "\n";

Private and protected methods 可以使用反射访问。您实际上可以使用相同的技术来访问非公共属性,但引用更简单、更轻量级。在anonymous functions 的 PHP 手册页的评论中,Martin Partel 给出了一个 FullAccessWrapper 类的示例,该类将非公共字段开放给公共访问。我不会在这里复制它(参见前面的两个链接),但这里是你如何使用它:

class Foo 
    private $_property='valid';

    public function method() 
        $this->_property = 'invalid';
        $self = new FullAccessWrapper($this);
        $finally = new Finally(function () use (&$self) 
                $self->_fixState();
        );
        /* ... */
    
    public function reportState() 
        return $this->_property;
    
    protected function _fixState() 
        $this->_property = 'valid';
    

$f = new Foo;
$f->method();
echo $f->reportState(), "\n";

try/finally

try 块至少需要一个 catch。如果你只想要try/finally,添加一个catch 块来捕获非Exception(PHP 代码不能抛出任何不是从Exception 派生的东西)或重新抛出捕获的异常。在前一种情况下,我建议将StdClass 作为一个成语,意思是“什么都抓不到”。在方法中,捕捉当前类也可以用来表示“不捕捉任何东西”,但使用StdClass在搜索文件时更简单,更容易找到。

try 
   $finally = new Finally(/*...*/);
   /* ... */
 catch (StdClass $exc) 

try 
   $finally = new Finally(/*...*/);
   /* ... */
 catch (RuntimeError $exc) 
    throw $exc

【讨论】:

我其实很喜欢这个,主要是因为即使抛出了你没有预料到的异常,它仍然可以工作,函数仍然会退出并销毁 finally 对象。要解决整个“直到函数退出才运行”的问题,您可以在异常之后使用unset $finally,这将提前调用析构函数。 +1,我喜欢。另外,由于某种原因,我们在这里使用 PHP 5.2.8,所以我必须补充:如果匿名函数不可用,您可以定义 finally 函数 before 创建 finally 对象,然后只需传入名称:function fooFinally() ; try $finally = new Finally(fooFinally); .... 哦,很好:这些“finally”语句即使在发生致命错误的情况下也会运行。 (通过尝试throw new StdClass; 进行测试) @Izkata:请注意,不鼓励使用裸词(即undefined constants),事实证明它们会产生E_NOTICE 级别的错误。按名称传递回调时,请使用字符串:new Finally('fooFinally')【参考方案4】:

这是我对缺少 finally 块的解决方案。它不仅为 finally 块提供了解决方法,它还扩展了 try/catch 以捕获 PHP 错误(以及致命错误)。我的解决方案如下所示(PHP 5.3):

_try(
    //some piece of code that will be our try block
    function() 
        //this code is expected to throw exception or produce php error
    ,

    //some (optional) piece of code that will be our catch block
    function($exception) 
        //the exception will be caught here
        //php errors too will come here as ErrorException
    ,

    //some (optional) piece of code that will be our finally block
    function() 
        //this code will execute after the catch block and even after fatal errors
    
);

您可以从 git hub 下载带有文档和示例的解决方案 - https://github.com/Perennials/travelsdk-core-php/tree/master/src/sys

【讨论】:

【参考方案5】:

如果有人仍在跟踪这个问题,您可能有兴趣查看 PHP wiki 中的(全新的)RFC for a finally language feature。作者似乎已经有了工作补丁,我相信该提案将从其他开发者的反馈中受益。

【讨论】:

【参考方案6】:

我刚刚写完一个更优雅的 Try Catch finally 类,它可能对你有用。有一些缺点,但可以解决。

https://gist.github.com/Zeronights/5518445

【讨论】:

【参考方案7】:
function _try(callable $try, callable $catch, callable $finally = null)

    if (is_null($finally))
    
        $finally = $catch;
        $catch = null;
    

    try
    
        $return = $try();
    
    catch (Exception $rethrow)
    
        if (isset($catch))
        
            try
            
                $catch($rethrow);
                $rethrow = null;
            
            catch (Exception $rethrow)  
        
    

    $finally();

    if (isset($rethrow))
    
        throw $rethrow;
    
    return $return;

使用闭包调用。第二个参数$catch 是可选的。例子:

_try(function ()

    // try
, function ($ex)

    // catch ($ex)
, function ()

    // finally
);

_try(function ()

    // try
, function ()

    // finally
);

正确处理任何地方的异常:

$try:异常将传递给$catch$catch 将首先运行,然后是 $finally。如果没有$catch,运行$finally后会重新抛出异常。 $catch$finally 将立即执行。 $finally 完成后将重新抛出异常。 $finally:异常将无障碍地分解调用堆栈。任何其他计划重新抛出的异常都将被丢弃。 :将返回来自$try 的返回值。

【讨论】:

以上是关于如何解决 PHP 中缺少 finally 块的问题?的主要内容,如果未能解决你的问题,请参考以下文章

finally块的问题(finally block does not complete normally)

如何解决作曲家问题 - laravel/framework [..] 需要 ext-mcrypt * -> 您的系统中缺少请求的 PHP 扩展 mcrypt

为啥 try..catch..finally 块的 finally 节在 catch 之前运行?

使用没有“catch”块的“try-finally”块

Finally语句块的运行

PHP 7:缺少 VCRUNTIME140.dll