我怎样才能解决PHP中缺少finally块的问题?

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中提供了将近四年的时间自从我发布这个答案后的约会......)

  • 这很容易,不要在try {}内返回.如果您没有注意到,我在第一句中特别指出,这不是一个解决方案,而是一个令人烦恼,繁琐的解决方法.缺乏finally块无法完全解决,因为提供最终保证需要解释器支持. (19认同)
  • 如果我在try {}内返回会发生什么?你的`if`不会执行. (18认同)
  • 所以你说的是PHP确实最终......它拼写为"if" (3认同)
  • @OneNerd:是的,即使从catch块内部返回,其他语言也会运行finally块.即使你扔进了catch区,他们也会运行它.如果您的程序转储代码,被杀死,如果操作系统崩溃,电源被切断等,它们显然不会运行它 (2认同)

out*_*tis 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.
            }
        }
    }
}
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)