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

5.5版之前的PHP最终没有障碍-即,在大多数明智的语言中,您可以执行以下操作:

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

PHP没有finally块的概念。

任何人都有解决这种烦人的语言漏洞的经验吗?

Kazar asked 2019-11-15T14:08:04Z
7个解决方案
61 votes

解决方案,没有。 烦人的解决方法,是的:

$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块:[https://wiki.php.net/rfc/finally](这只花了几年时间……在5.5 RC中可用将近四年) 到我发布此答案的日期为止...)

Mihai Limbășan answered 2019-11-15T14:08:33Z
9 votes

RAII习惯用法为try块提供了代码级替代。 创建一个包含可调用类的类。 在分解器中,调用可调用对象。

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没有变量的块作用域,因此try将在函数退出或(在全局作用域中)关闭序列之前不起作用。 例如,以下内容:

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";

将导致输出:

Creating global Finally.
Foo::useTry done.
Finally for method run.
A whole bunch more work done by the script.
Global Finally finally run.

$ this

PHP 5.3闭包无法访问try(在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中这种方法的最大问题是,最终关闭无法访问对象的私有字段和受保护字段。 与访问try一样,此问题在PHP 5.4中得以解决。 目前,可以使用引用来访问私有和受保护的属性,如Artefacto在他对本站点其他地方有关该主题的问题的回答中所示。

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";

可以使用反射来访问私有和受保护的方法。 实际上,您可以使用相同的技术来访问非公共属性,但是引用更简单,更轻量。 在关于匿名函数的PHP手册页的评论中,Martin Partel给出了一个try类的示例,该类打开了非公共字段以供公共访问。 我不会在这里重现它(请参见前面的两个链接),但是这里是您的用法:

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

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

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

try {
   $finally = new Finally(/*...*/);
   /* ... */
} catch (RuntimeError $exc) {
    throw $exc
}
outis answered 2019-11-15T14:10:27Z
2 votes

这是我对缺少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]

bobef answered 2019-11-15T14:11:05Z
1 votes

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

好的书主张不要将finally块用于释放资源以外的任何其他用途,因为您不确定如果发生令人讨厌的事情,它将无法执行。 称其为令人讨厌的漏洞实在是夸大其词。相信我,有很多非常好的代码是用语言编写的,没有最终阻塞。 :)

最终的目的是不管try块是否成功执行。

Csaba Kétszeri answered 2019-11-15T14:11:48Z
1 votes
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;
}

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

_try(function ()
{
    // try
}, function ($ex)
{
    // catch ($ex)
}, function ()
{
    // finally
});

_try(function ()
{
    // try
}, function ()
{
    // finally
});

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

  • $try:异常将传递给$finally。首先将运行$finally,然后再运行$finally。如果没有$catch,则在运行$finally后将重新引发异常。
  • $try$finally将立即执行。 $finally完成后,将重新引发异常。
  • $try:异常将无中断地破坏调用堆栈。 计划重新抛出的任何其他异常将被丢弃。
  • 无:将返回$try的返回值。
Zenexer answered 2019-11-15T14:13:02Z
0 votes

如果有人仍在跟踪这个问题,您可能有兴趣检查(全新的)RFC,以了解PHP Wiki中的最终语言功能。 作者似乎已经有了有效的补丁程序,并且我确信该提案将从其他开发人员的反馈中受益。

Tobias Gies answered 2019-11-15T14:13:32Z
0 votes

我刚刚写了一个更优雅的Try Catch Final类,可能对您有用。 有一些缺点,但是可以解决。

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

Ozzy answered 2019-11-15T14:14:11Z
translate from https://stackoverflow.com:/questions/927214/how-can-i-get-around-the-lack-of-a-finally-block-in-php