在 PHP 中如何使用关键字 'finally'?

How is the keyword 'finally' meant to be used in PHP?

所以,我今天在 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 解释器停止脚本。好的。从我读到的内容来看,finally 使我们能够执行函数 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 块将用作万一未捕获到异常的最后手段,即使情况并非如此,那为什么要使用 运行 呢?

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

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

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

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

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

finally 每执行一次*

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

*如果 trycatch 块执行 die/exit.

,它将 而不是 运行

异常

我看到的一个常见用途是关闭长 运行ning worker 中的数据库连接 - 你希望每次都发生这种情况(有或没有例外)所以你不会以悬空结束阻止数据库服务器接受新连接的连接。

考虑这个 pseudo-code:

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

这将使其在有或没有异常的情况下关闭连接。

Return

其中一个比较模糊的行为是它能够在 return 语句之后执行代码。

这里你可以在函数returned:

后设置一个变量
function foo(&$x)
{
    try {
        $x = 'trying';
        return $x;
    } finally {
        $x = 'finally';
    }
}

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

tryingfinally

但作业将是 return在 try:

中编辑的内容
$bar = foo($bar);
echo $bar . $bar;

tryingtrying

和 returning 最终覆盖了 try:

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

echo baz();

finally

注意 此行为在 php 5:

中有所不同

finallyfinally
finallyfinally
finally

https://3v4l.org/biO4e

优秀Return

你可以让它看起来像同时抛出 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();
}

caught finally

https://3v4l.org/Jknpm

* 死

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

try {
    echo "trying";
    die;
} finally {
    echo "finally";
}

echo "end";

trying

https://3v4l.org/pc9oc

† 硬件故障

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

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
}

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

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