如何解决 PHP 中缺少 finally 块的问题?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/927214/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I get around the lack of a finally block in PHP?
提问by Alistair Evans
PHP prior to version 5.5 has no finally block - i.e., whereas in most sensible languages, you can do:
5.5 版之前的 PHP 没有 finally 块 - 即,而在大多数明智的语言中,您可以执行以下操作:
try {
//do something
} catch(Exception ex) {
//handle an error
} finally {
//clean up after yourself
}
PHP has no notion of a finally block.
PHP 没有 finally 块的概念。
Anyone have experience of solutions to this rather irritating hole in the language?
任何人都有解决这个相当恼人的语言漏洞的经验?
回答by Mihai Limb??an
Solution, no. Irritating cumbersome workaround, yes:
解决方案,没有。烦人的繁琐解决方法,是的:
$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);
}
Yucky, but should work.
恶心,但应该工作。
Please note: PHP 5.5 finally (ahem, sorry) added a finally block: https://wiki.php.net/rfc/finally(and it only took a few years... available in the 5.5 RC almost four years to the date since I posted this answer...)
请注意:PHP 5.5 终于(咳咳,抱歉)添加了一个 finally 块:https: //wiki.php.net/rfc/finally(而且只用了几年时间......在 5.5 RC 中可用近四年到自从我发布这个答案以来的日期......)
回答by outis
The RAIIidiom offers a code-level stand-in for a finallyblock. Create a class that holds callable(s). In the destuctor, call the callable(s).
在RAII成语提供代码级替身的finally块。创建一个包含可调用对象的类。在 destructor 中,调用 callable(s)。
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.
}
}
}
}
Coordination
协调
Note that PHP doesn't have block scope for variables, so Finallywon't kick in until the function exits or (in global scope) the shutdown sequence. For example, the following:
请注意,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";
will result in the output:
将导致输出:
Creating global Finally. Foo::useTry done. Finally for method run. A whole bunch more work done by the script. Global Finally finally run.
$this
$this
PHP 5.3 closures can't access $this(fixed in 5.4), so you'll need an extra variable to access instance members within some finally-blocks.
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() {/*...*/}
}
Private and Protected Fields
私有和受保护的字段
Arguably the biggest problem with this approach in PHP 5.3 is the finally-closure can't access private and protected fields of an object. Like accessing $this, this issue is resolved in PHP 5.4. For now, private and protected propertiescan be accessed using references, as Artefacto shows in his answerto a question on this very topic elsewhere on this site.
可以说 PHP 5.3 中这种方法的最大问题是 finally-closure 无法访问对象的私有和受保护字段。与访问一样$this,这个问题在 PHP 5.4 中得到解决。目前,可以使用引用访问私有和受保护的属性,正如 Arteacto 在他对本网站其他地方有关此主题的问题的回答中所示。
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 methodscan be accessed using reflection. You can actually use the same technique to access non-public properties, but references are simpler and more lightweight. In a comment on the PHP manual page for anonymous functions, Martin Partel gives an example of a FullAccessWrapperclass that opens up non-public fields to public access. I won't reproduce it here (see the two previous links for that), but here is how you'd use it:
可以使用反射访问私有和受保护的方法。您实际上可以使用相同的技术来访问非公共属性,但引用更简单、更轻量级。在匿名函数的 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/finally
tryblocks require at least one catch. If you only want try/finally, add a catchblock that catches a non-Exception(PHP code can't throw anything not derived from Exception) or re-throw the caught exception. In the former case, I suggest catching StdClassas an idiom meaning "don't catch anything". In methods, catching the current class could also be used to mean "don't catch anything", but using StdClassis simpler and easier to find when searching files.
try块至少需要一个catch. 如果您只想要try/finally,请添加一个catch捕获非Exception(PHP 代码不能抛出任何非派生自Exception)的块或重新抛出捕获的异常。在前一种情况下,我建议StdClass将 catch 作为一个成语,意思是“什么也别抓”。在方法中,捕获当前类也可以用来表示“不捕获任何东西”,但StdClass在搜索文件时使用更简单、更容易找到。
try {
$finally = new Finally(/*...*/);
/* ... */
} catch (StdClass $exc) {}
try {
$finally = new Finally(/*...*/);
/* ... */
} catch (RuntimeError $exc) {
throw $exc
}
回答by bobef
Here is my solution to the lack of finally block. It not only provides a work around for the finally block, it also extends the try/catch to catch PHP errors (and fatal errors too). My solution looks like this (PHP 5.3):
这是我对缺少 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
}
);
You can download the solution with documentation and examples from git hub - https://github.com/Perennials/travelsdk-core-php/tree/master/src/sys
您可以从 git hub 下载带有文档和示例的解决方案 - https://github.com/Perennials/travelsdk-core-php/tree/master/src/sys
回答by Zenexer
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;
}
Call using closures. Second parameter, $catch, is optional. Examples:
使用闭包调用。第二个参数$catch, 是可选的。例子:
_try(function ()
{
// try
}, function ($ex)
{
// catch ($ex)
}, function ()
{
// finally
});
_try(function ()
{
// try
}, function ()
{
// finally
});
Properly handles exceptions everywhere:
在任何地方正确处理异常:
$try: Exception will be passed to$catch.$catchwill run first, then$finally. If there is no$catch, exception will be rethrown after running$finally.$catch:$finallywill execute immediately. Exception will be rethrown after$finallycompletes.$finally: Exception will break down the call stack unimpeded. Any other exceptions scheduled for rethrow will be discarded.- None: Return value from
$trywill be returned.
$try: 异常将传递给$catch.$catch将首先运行,然后$finally。如果没有$catch,运行后将重新抛出异常$finally。$catch:$finally会立即执行。$finally完成后将重新抛出异常。$finally: 异常会畅通无阻地分解调用堆栈。计划重新抛出的任何其他异常都将被丢弃。- None:将返回来自的返回值
$try。
回答by Csaba Kétszeri
As this is a language construct, you won't find an easy solution for this. You can write a function and call it as the last line of your try block and last line before rethrowing the excepion in the try block.
由于这是一种语言结构,因此您找不到简单的解决方案。您可以编写一个函数并将其作为 try 块的最后一行和在 try 块中重新抛出异常之前的最后一行调用。
Good books argues against using finally blocks for any other than freeing resource as you can not be sure it will execute if something nasty happens. Calling it an irritating hole is quite an overstatement. Believe me, a hell lot of exceptionally good code is written in languages without finally block. :)
好书反对将 finally 块用于除释放资源之外的任何其他用途,因为如果发生令人讨厌的事情,您无法确定它会执行。称它为一个令人讨厌的漏洞是一种夸大其词的说法。相信我,很多非常好的代码都是用没有 finally 块的语言编写的。:)
The point of finally is to execute no matter if the try block was successfull or not.
finally 的重点是无论 try 块是否成功都会执行。
回答by Tobias Gies
If anyone is still keeping track of this question, you might be interested in checking out the (brand new) RFC for a finally language featurein the PHP wiki. The author already seems to have working patches, and I'm sure the proposal would benefit from other developers' feedback.
如果有人仍在跟踪这个问题,您可能有兴趣查看PHP wiki 中的 finally 语言功能的(全新)RFC。作者似乎已经有了可用的补丁,我相信该提案将从其他开发人员的反馈中受益。
回答by Ozzy
I just finished writing a more elegant Try Catch Finally class which may be of use to you. There are some drawbacks but they can be worked around.
我刚刚完成了一个更优雅的 Try Catch finally 类的编写,它可能对您有用。有一些缺点,但它们可以解决。

