56

5.5 版之前的 PHP 没有 finally 块 - 即,在大多数明智的语言中,您可以这样做:

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

PHP 没有 finally 块的概念。

任何人都有解决语言中这个相当恼人的漏洞的经验吗?

4

7 回答 7

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);
}

恶心,但应该工作。

请注意:PHP 5.5 finally(咳咳,抱歉)添加了 finally 块:https ://wiki.php.net/rfc/finally (只花了几年时间......在 5.5 RC 中可用将近四年自我发布此答案以来的日期...)

于 2009-05-29T17:36:58.233 回答
9

RAII习惯用法为块提供代码级替代finally。创建一个包含可调用对象的类。在析构函数中,调用可调用对象。

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

将导致输出:

最后创建全局。
Foo::use 尝试完成。
最后用于方法运行。
脚本完成了一大堆工作。
Global 终于运行了。

$这个

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 中得到解决。目前,可以使用引用访问私有和受保护的属性,正如 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 给出了一个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块来捕获非Exception(PHP 代码不能抛出任何不是从 派生的东西Exception)或重新抛出捕获的异常。在前一种情况下,我建议StdClass将 catch 作为一个成语,意思是“不要抓到任何东西”。在方法中,捕获当前类也可以用来表示“不捕获任何东西”,但是StdClass在搜索文件时使用更简单,更容易找到。

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

try {
   $finally = new Finally(/*...*/);
   /* ... */
} catch (RuntimeError $exc) {
    throw $exc
}
于 2012-02-11T22:48:30.473 回答
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

于 2012-05-30T18:43:52.333 回答
1

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

好书反对将 finally 块用于释放资源之外的任何其他内容,因为如果发生令人讨厌的事情,您无法确定它是否会执行。称其为令人讨厌的漏洞是一种夸大其词的说法。相信我,很多非常好的代码都是用没有 finally 块的语言编写的。:)

finally 的重点是无论 try 块是否成功都执行。

于 2009-05-29T19:08:06.523 回答
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: 异常会畅通无阻地分解调用堆栈。任何其他计划重新抛出的异常都将被丢弃。
  • None : 返回值 from$try将被返回。
于 2013-07-14T10:43:57.797 回答
0

如果有人仍在跟踪这个问题,您可能有兴趣查看(全新的)RFC 以了解 PHP wiki 中的 finally 语言功能。作者似乎已经有了工作补丁,我相信该提案将从其他开发人员的反馈中受益。

于 2012-07-31T08:05:55.470 回答
0

我刚刚写完一个更优雅的 Try Catch finally 类,它可能对你有用。有一些缺点,但可以解决。

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

于 2013-05-04T19:23:11.370 回答