Kaz*_*zar 56 php exception resource-cleanup
版本5.5之前的PHP没有最终块 - 即,在大多数敏感语言中,您可以执行以下操作:
try {
//do something
} catch(Exception ex) {
//handle an error
} finally {
//clean up after yourself
}
Run Code Online (Sandbox Code Playgroud)
PHP没有finally块的概念.
任何人都有解决这种语言相当恼人的漏洞的经验吗?
Mih*_*șan 61
解决方案,没有.刺激繁琐的解决方法,是的:
$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);
}
Run Code Online (Sandbox Code Playgroud)
呀,但应该工作.
请注意:PHP 5.5最终(咳咳,对不起)添加了一个finally块:https://wiki.php.net/rfc/finally(并且它只用了几年......在5.5 RC中提供了将近四年的时间自从我发布这个答案后的约会......)
在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.
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,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";
Run Code Online (Sandbox Code Playgroud)
将导致输出:
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() {/*...*/}
}
Run Code Online (Sandbox Code Playgroud)
可以说,PHP 5.3中这种方法的最大问题是finally-closure无法访问对象的私有和受保护字段.与访问一样$this
,此问题在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";
Run Code Online (Sandbox Code Playgroud)
可以使用反射访问专用和受保护的方法.实际上,您可以使用相同的技术来访问非公共属性,但引用更简单,更轻量级.在匿名函数的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";
Run Code Online (Sandbox Code Playgroud)
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
}
Run Code Online (Sandbox Code Playgroud)