【问题标题】:How can I get around the lack of a finally block in PHP?如何解决 PHP 中缺少 finally 块的问题?
【发布时间】:2010-10-29 22:59:59
【问题描述】:

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

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

PHP 没有 finally 块的概念。

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

【问题讨论】:

  • finally 已获得批准,应该在 PHP 5.5 中可用。 wiki.php.net/rfc/finally
  • 好消息,我可能会在 PHP 5.5 发布后更新问题以表明 finally 块的可用性。

标签: php exception resource-cleanup


【解决方案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 finally(咳咳,抱歉)添加了 finally 块:https://wiki.php.net/rfc/finally(而且只用了几年...在 5.5 RC 中可用将近四年自我发布此答案以来的日期...)

【讨论】:

  • 所以你的意思是 PHP 终于有了……它的拼写是“if”
  • 如果我在 try{} 内返回会发生什么?你的if 不会执行。
  • 这很简单,不要在 try{} 内返回。如果您没有注意到,我在第一句话中特别指出,这不是一个解决方案,而是一个烦人、繁琐的解决方法。由于提供 finally 保证需要解释器支持,因此无法完全解决缺少 finally 块的问题。
  • 我承认我不是专家级的 try/catch/finally 用户,但为什么不直接在 catch 块中抛出异常呢?是因为您可能会在 throw() 之前运行一些其他代码作为清理代码吗?而且,如果(如 Rob 所说)你有一个“返回 X”;在 try 块中,其他语言在返回之前是否仍会运行 finally 块(如果是这样,那很好,尽管当我看到 return 语句时,我假设在那之后没有其他东西运行?不过对我来说非常有趣。
  • @OneNerd:是的,即使从 catch 块内部返回,其他语言也会运行 finally 块。即使您将其扔进 catch 块内,它们也会运行它。如果您的程序转储代码、被杀死、操作系统崩溃、断电等,它们显然不会运行它
【解决方案2】:

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

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

finally的意义在于不管try块成功与否都执行。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 我其实很喜欢这个,主要是因为即使抛出了你没有预料到的异常,它仍然可以工作,函数仍然会退出并销毁 finally 对象。要解决整个“直到函数退出才运行”的问题,您可以在异常之后使用unset $finally,这将提前调用析构函数。
    • +1,我喜欢。另外,由于某种原因,我们在这里使用 PHP 5.2.8,所以我必须补充:如果匿名函数不可用,您可以定义 finally 函数 before 创建 finally 对象,然后只需传入名称:function fooFinally() {}; try { $finally = new Finally(fooFinally); ....
    • 哦,很好:这些“finally”语句即使在发生致命错误的情况下也会运行。 (通过尝试throw new StdClass; 进行测试)
    • @Izkata:请注意,不鼓励使用裸词(即undefined constants),事实证明它们会产生E_NOTICE 级别的错误。按名称传递回调时,请使用字符串:new Finally('fooFinally')
    【解决方案4】:

    这是我对缺少 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

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

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

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

        【讨论】:

          【解决方案7】:
          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 的返回值。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-06-20
            • 1970-01-01
            • 2011-10-01
            • 2019-07-12
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多