【问题标题】:How is the keyword 'finally' meant to be used in PHP?关键字“finally”是如何在 PHP 中使用的?
【发布时间】:2017-05-27 18:33:57
【问题描述】:

所以,我今天一直在阅读 PHP 在线手册上的异常,并意识到我还没有理解 finally 关键字的目的或真正必要性。我在这里阅读了一些帖子,所以我的问题略有不同。

我明白我们可以这样使用finally:

function hi(){
    return 'Hi';
}


try {
    throw new LogicException("Throw logic \n");
} catch (InvalidArgumentException $e) {
    echo $e->getMessage(); 
}

echo hi();

输出:

Fatal error:  Uncaught LogicException: Throw Logic in C:\Users\...a.php:167
Stack trace:
#0 {main}
  thrown in C:\Users\...a.php on line 167

所以,在这种情况下,函数 hi();没有被执行并且有充分的理由。我了解是否未处理异常 php 解释器停止脚本。好的。到目前为止,从我阅读的内容来看,最终使我们能够执行函数 hi();即使 异常没有被处理(尽管我不知道为什么)

所以,这个我明白了。

try {
    throw new LogicException("Throw logic \n");
} catch (InvalidArgumentException $e) {
    echo $e->getMessage(); 
}finally{
    echo hi();
}

输出:

Hi
Fatal error:  Uncaught LogicException: Throw Logic in C:\Users\...a.php:167
Stack trace:
#0 {main}
  thrown in C:\Users\...a.php on line 167

这应该是异常错误以及来自函数的“hi”消息,即使是那些我不知道有任何用法的消息。但是我不明白这一点,即使我们用 catch (LogicException $e) 捕获 LogicException 并且没有抛出异常,我们仍然会看到函数正在执行,并且会看到“hi”消息。如本例所示

try {
    throw new LogicException("Throw logic \n");
} catch (LogicException $e) {
    echo $e->getMessage(); 
}finally{
    echo hi();
}

输出

// Throw logic 
// Hi

所以,即使我们没有未捕获的异常,我们仍然可以看到函数 hi() 被执行。为什么以及这样做有什么用? 我认为 finally 块将用作万一没有捕获异常的最后手段,即使不是这种情况,那为什么还要运行它呢?

【问题讨论】:

  • 您使用的是什么版本的 PHP?真正的 finally 功能直到 PHP 5.5 才添加。
  • @AlecGordon php 7.0.1
  • 哦,那很奇怪。根据this analysis,应在未捕获异常之前打印 finally 块。
  • finally 中的代码在try + catch 中的所有代码之后执行。

标签: php oop exception try-catch-finally


【解决方案1】:

finally 每* 次执行一次

无论错误、异常,甚至是return 语句,finally 代码块都会运行。

*如果trycatch 块执行die/exit,它将运行。

异常

我看到的一个常见用途是关闭长时间运行的工作程序中的数据库连接 - 您希望每次都发生这种情况(有或没有例外),这样您就不会最终得到一个阻止数据库服务器的悬空连接接受新的连接。

考虑这个伪代码:

try {
   $database->execute($sql);
} finally {
   $database->close();
}

在这里,我们将始终关闭数据库连接。如果是正常查询,我们在成功后关闭连接,脚本会继续执行。

如果是错误的查询,那么在抛出异常后我们仍然关闭,未捕获的异常会导致脚本停止。

这是一个 catch 进行日志记录的示例。

try {
   $database->execute($sql);
} catch (Exception $exception) {
   $logger->error($exception->getMessage(), ['sql' => $sql]);
   throw $exception;
} finally {
   $database->close();
}

这将使它关闭连接,无论是否有异常。

返回

其中一个比较晦涩的行为是它能够在返回语句之后执行代码。

这里可以在函数返回后设置一个变量:

function foo(&$x)
{
    try {
        $x = 'trying';
        return $x;
    } finally {
        $x = 'finally';
    }
}

$bar = 'main';
echo foo($bar) . $bar;

终于尝试

但是分配将是 try 中返回的内容:

$bar = foo($bar);
echo $bar . $bar;

尝试尝试

并且在 finally 中返回会覆盖 try 中的返回:

function baz()
{
    try {
        return 'trying';
    } finally {
        return 'finally';
    }
}

echo baz();

终于

注意这种行为在 php 5 中有所不同:

终于来了
终于终于
终于

https://3v4l.org/biO4e

异常回报

您可以让它看起来像同时抛出 2 个异常:

try {
    throw new Exception('try');
} finally {
    throw new Exception('finally');
}
Fatal error: Uncaught Exception: try in /in/2AYmF:4
Stack trace:
#0 {main}

Next Exception: finally in /in/2AYmF:6
Stack trace:
#0 {main}
  thrown in /in/2AYmF on line 6

Process exited with code 255.

https://3v4l.org/2AYmF

但您无法真正捕捉到我知道的“第一个”异常,以便在运行时做任何有趣的事情:

try {
    try {
        throw new Exception('try');
    } finally {
        throw new Exception('finally');
    }
} catch (Exception $exception) {
    echo 'caught ' . $exception->getMessage();
}

终于抓到了

https://3v4l.org/Jknpm

* 死去

如果你exit or die,那么finally 块将不会执行。

try {
    echo "trying";

    die;
} catch(Exception $e) {
    echo "caught";
} finally {
    echo "finally";
}

echo "end";

尝试

https://3v4l.org/pc9oc

† 硬件故障

最后,你应该明白,如果有人拔掉你服务器上的电源插头,finally 块将不会执行 ? 虽然我还没有测试过,但我希望内存耗尽也会跳过它。

【讨论】:

  • 我现在明白了,但用例似乎很少见。例如,在您的示例中,您仍然可以在 throw 语句之前关闭数据库,并且它会达到同样的效果。
  • @mvrht: 当然你可以在throwstatement without 之前使用finally 关闭数据库连接,但重点是在这种情况下:首先,也许你不要捕获 every 异常,而只是捕获某种类型,那么您就不会到达 close() 对其他异常的调用。其次,即使您确实捕获了每个异常,您也需要在某处(在tryblock 的末尾或catch 块之后)使用重复的代码行来调用close()。将该调用放在finally 块中,您只需编写一次,并且在每种情况下都会执行(无论是否发生异常,是否被捕获)。
  • 如果有人问,finally 每次在 trycatch 内执行 except when there isexit()/die()块(被执行的那个)
  • @Foxel 不错的见解!我会在我的回答中包含这个:)
  • @James ? 我们必须从同一块布上剪下来,因为那让我无论在哪里看到它都会让我很生气,所以我故意让它成为一个混蛋。但我现在意识到我的方式不成熟,并添加了两个适当的参考 ⛪
【解决方案2】:

最后应该包含任何需要执行的代码,无论是否有异常。

没有finally:

try {
   $handle = fopen("file.txt");
   //Do stuff
   fclose($handle);
   return something;
} catch (Exception $e) {
   // Log
   if (isset($handle) && $handle !== false) {
      fclose($handle);
   }     
}

最后:

try {
   $handle = fopen("file.txt");
   return something;
} catch (Exception $e) {
   // Log
} finally {
   if (isset($handle) && $handle !== false) {
      fclose($handle);
   }     
}

在函数返回后需要释放资源的情况下提供一些整理。

这在以下情况下变得更加有用:

 try {
     $handle = fopen("file.txt");
     if (case1) { return result1; }  
     if (case2) { return result2; }
     if (case3) { return result3; }
     if (case4) { return result4; }

 } finally {
     if (isset($handle) && $handle !== false) {
          fclose($handle);
       }    
 }

在这种情况下,您可以在每次返回之前将所有必需的 fclose 调用减少为单个 fclose 调用,该调用将在方法返回之前但在任何其他代码之后执行。

【讨论】:

    【解决方案3】:
    try {
        throw new LogicException("Throw logic \n"); -> LogicException thrown
    } catch (InvalidArgumentException $e) { -> LogicException not catched
        echo $e->getMessage(); 
    }finally{
        echo hi(); -> code executed. "Hi" printed out
    }
    
    LogicException is here -> Fatal error
    

    所以在这种情况下:

    try {
        throw new LogicException("Throw logic \n"); -> LogicException thrown
    } catch (InvalidArgumentException $e) { -> LogicException not catched
        echo $e->getMessage(); 
    }finally{
        echo hi(); -> code executed
        die();
    }
    

    不会出现致命错误,因为 die 语句 最后一个变化:

    try {
        throw new LogicException("Throw logic \n"); -> LogicException thrown
    } catch (InvalidArgumentException $e) { -> LogicException not catched
        echo $e->getMessage(); 
    } catch (LogicException $e) { -> LogicException catched
        echo $e->getMessage(); 
    }finally{
        echo hi(); -> code executed
    }
    

    【讨论】:

      【解决方案4】:

      我做了一个小单元测试来展示它是如何工作的

          $a = 'a';
          try {
              $a .= 'b';
          } catch (Exception $ex) {
              $a .= 'e';
          } finally {
              $a .= 'f';
          }
          $a .= 'x';
      
          $this->assertSame('abfx', $a);
      
      
          $a = 'a';
          try {
              $a .= 'b';
              throw new Exception();
              $a .= '1';
          } catch (Exception $ex) {
              $a .= 'e';
          } finally {
              $a .= 'f';
          }
          $a .= 'x';
      
          $this->assertSame('abefx', $a);
      
      
          $a = 'a';
          try {
              try {
                  $a .= 'b';
                  throw new Exception();
                  $a .= '1';
              } catch (Exception $ex) {
                  $a .= 'e';
                  throw $ex;
                  $a .= '2';
              } finally {
                  $a .= 'f';
              }
              $a .= 'x';
          } catch (Exception $ex) {
              $a .= 'z';
          }
      
          $this->assertSame('abefz', $a);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-03-23
        • 2013-09-20
        • 2011-12-10
        • 2017-11-25
        • 2019-12-07
        • 2013-09-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多