【问题标题】:Why is PDO throwing an ErrorException rather than a PDOException?为什么 PDO 会抛出 ErrorException 而不是 PDOException?
【发布时间】:2019-08-03 08:11:25
【问题描述】:

我让 Sentry 跟踪我的 PHP 应用程序中未捕获的异常,并且我注意到来自 PDO 的一个特殊的未捕获异常。代码如下所示:

   /**
    * @return boolean TRUE if the connection to the database worked; FALSE otherwise. 
    */
   public function verifyDatabase() {
       try{ 
           $this->pdo->query('SELECT 1');
           return true;
       }
       catch (\PDOException $e) {
           echo 'Lost connection to database: ' . $e->getMessage() . PHP_EOL;
           return false;
       }
    }

这应该会捕获诸如“MySQL 服务器已消失”之类的错误,并且它确实可以在我的开发机器上运行。但是,Sentry 最近记录了这个错误:

错误异常 PDO::query(): MySQL 服务器已经消失

根据哨兵的说法,这是由上面的$this->pdo->query('SELECT 1'); 语句抛出的。像这样的错误应该被try/catch 捕获。为什么 PDO 会抛出 ErrorException 而不是 PDOException

【问题讨论】:

  • 因为它是。时期。我从不打扰 PDOException,它会在捕获 Exception 时被卷起,而且我还会捕获 Throwable 以避免(有时)在关键 API 调用上出现可怕的 WSOD 或 500。

标签: php mysql exception pdo


【解决方案1】:

好的,我想我已经想通了。看来this is related to a bug in which the PDO MySQL driver emits warnings even when they are supposed to be disabled(另见:this answer)。我相信 Sentry 会将这些视为错误。

我终于能够通过修改 Bill Karwin 的测试脚本来复制并解决这个问题:

// Initialize the Sentry reporting client
$ravenClient = new \Raven_Client(SENTRY_KEY);
$ravenClient->install();

echo 'Connecting...' . PHP_EOL;

$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

echo 'Connected. Waiting...' . PHP_EOL;

sleep(20); // during this sleep, I stop my MySQL Server instance.

echo 'Querying...' . PHP_EOL;

try {
    $result = $pdo->query("SELECT 1");
}
catch(\PDOException $e) {
    echo 'Caught PDOException ' . $e->getMessage() . PHP_EOL;
}

这将打印以下内容:

Connecting...
Connected. Waiting...
Querying...
PHP Warning:  PDO::query(): MySQL server has gone away in /home/xxx/src/test.php on line 37
PHP Stack trace:
PHP   1. {main}() /home/xxx/src/test.php:0
PHP   2. PDO->query() /home/xxx/src/test.php:37
Caught PDOException SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
Done.

Sentry 将在query() 行上记录一个ErrorException

我能够通过实施 jferrerthe PHP bug report 上发布的“解决方案”来解决这个问题。

// Convert NOTICE, WARNING, ... in Exceptions
$convertErrorToException = function ($level, $message, $file, $line){
    throw new ErrorException($message, 0, $level, $file, $line);
};

// The $previousErrorHandler will contain Sentry's handler
$previousErrorHandler = set_error_handler($convertErrorToException);

try {
    $result = $pdo->query("SELECT 1");
}
catch(\PDOException $e) {
    echo 'Caught PDOException ' . $e->getMessage() . PHP_EOL;
}
catch(\ErrorException $e) {
    echo 'Caught ErrorException ' . $e->getMessage() . PHP_EOL;
}

// Restore Sentry as the default handler
set_error_handler($previousErrorHandler);

这只会引发并捕获 ErrorException:

Connecting...
Connected. Waiting...
Querying...
Caught ErrorException PDO::query(): MySQL server has gone away
Done.

【讨论】:

    【解决方案2】:

    我无法重现 ErrorException。

    $pdo = new PDO('mysql:host=127.0.0.1;dbname=test', ..., ...);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    
    sleep(20); // during this sleep, I stop my MySQL Server instance.
    
    $result = $pdo->query("SELECT 1");
    

    输出:

    Warning: PDO::query(): MySQL server has gone away
    
    Warning: PDO::query(): Error reading result set's header
    
    Fatal error: Uncaught PDOException: SQLSTATE[HY000]: General error: 2006 MySQL server has gone away
    Stack trace:
    #0 /Users/bkarwin/Documents/SO/pdo.php(8): PDO->query('SELECT 1')
    

    这表明当服务器离开时它会抛出 PDOException,而不是 ErrorException。

    用 MySQL 5.6.37 和 PHP 7.1.23 测试。

    我想知道您在问题中显示的代码是否实际上是已部署并向 Sentry 抛出异常的代码。也许你有一些类似的代码:

       catch (\PDOException $e) {
           throw ErrorException($e->getMessage());
       }
    

    在您的 verifyDatabase() 函数中,或者在调用 verifyDatabase() 的代码中。换句话说,当 verifyDatabase() 返回 false 时,您的应用会做什么?

    【讨论】:

    • 这是一个应该无限期运行的工作脚本。当verifyDatabase() 返回 false 时,脚本只是存在并由主管进程重新启动,以便重新连接到数据库。
    • 好的,当前部署的代码是否有任何抛出 ErrorException 的情况?不要假设 - 去检查它。
    猜你喜欢
    • 2016-09-24
    • 2012-04-09
    • 2013-02-18
    • 2014-06-22
    • 2023-02-04
    • 1970-01-01
    相关资源
    最近更新 更多