我该如何解决PHP中缺少最终块的问题?

2022-08-30 10:02:44

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

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

PHP没有最终块的概念。

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


答案 1

解决方案,没有。烦人的繁琐解决方法,是的:

$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终于(哎呀,对不起)添加了一个最后的块:https://wiki.php.net/rfc/finally(只花了几年时间......自我发布此答案以来,在5.5 RC中可用近四年...)


答案 2

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

将产生以下结果:

Creating global Finally.
Foo::useTry done.
Finally for method run.
A whole bunch more work done by the script.
Global Finally finally run.

$this

PHP 5.3 闭包无法访问(在 5.4 中已修复),因此您需要一个额外的变量来访问某些最终块中的实例成员。$this

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 中这种方法的最大问题是最终闭包无法访问对象的私有和受保护字段。与访问一样,此问题已在 PHP 5.4 中得到解决。目前,可以使用引用访问私有和受保护的属性,正如Artefacto在回答有关此主题的问题的站点上所示。$this

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块至少需要一个 。如果你只想要,添加一个块来捕获非 -(PHP 代码不能抛出任何不是从 中派生的)或重新抛出捕获的异常。在前一种情况下,我建议将捕获作为成语,意思是“不要捕获任何东西”。在方法中,捕获当前类也可用于表示“不捕获任何内容”,但在搜索文件时使用更简单,更容易找到。catchtry/finallycatchExceptionExceptionStdClassStdClass

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

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

推荐