版本5.5之前的PHP没有最终阻止 - 即,在大多数敏感语言中,您可以这样做:
try {
//do something
} catch(Exception ex) {
//handle an error
} finally {
//clean up after yourself
}
PHP没有finally块的概念。
任何人都有解决这种语言相当恼人的漏洞的经验吗?
答案 0 :(得分:59)
解决方案,没有。刺激繁琐的解决方法,是的:
$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中可用了将近四年到我发布这个答案后的日期......)
答案 1 :(得分:9)
RAII成语为finally
块提供了代码级替代。创建一个包含可调用的类。在destuctor中,调用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.
}
}
}
}
请注意,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";
将导致输出:
Creating global Finally. Foo::useTry done. Finally for method run. A whole bunch more work done by the script. Global Finally finally run.
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
的{{1}}块(PHP代码不能抛出任何不是从Exception
派生的内容)或重新抛出抓住了例外。在前一种情况下,我建议将Exception
作为一个成语,意思是“不要抓住任何东西”。在方法中,捕获当前类也可以用来表示“不捕获任何东西”,但在搜索文件时使用StdClass
更简单,更容易找到。
StdClass
答案 2 :(得分:2)
这是我对缺乏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
答案 3 :(得分:1)
由于这是一种语言结构,因此您无法找到一个简单的解决方案。 您可以编写一个函数,并在重新抛出try块中的例外之前将其作为try块的最后一行和最后一行调用。
好书反对使用finally块除了释放资源之外的其他任何东西,因为你不能确定如果发生了令人讨厌的事情它会执行。把它称为一个恼人的漏洞是一种夸大其词。 相信我,一个非常好的代码是用语言编写的,没有最终阻止。 :)
无论try块是否成功,最终都要执行。
答案 4 :(得分:1)
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
的返回值。答案 5 :(得分:0)
如果有人仍在跟踪此问题,您可能有兴趣查看PHP维基中的(全新)RFC for a finally language feature。作者似乎已经有了工作补丁,我确信该提案会受益于其他开发人员的反馈。
答案 6 :(得分:0)
我刚刚编写了一个更优雅的Try Catch Finally类,它可能对你有用。有一些缺点,但它们可以解决。