模拟 PDO 获取失败的情况

Simulate a PDO fetch failure situation

符合 php 文档,PDO 方法 fetch() return 值 FALSE 两者都没有找到记录 AND 失败(例如,当数据库访问出现问题时)。

假设,我将 PHP 错误报告系统设置为在失败时抛出异常:

PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION

我需要一个案例,一个fetch()方法会抛出异常的情况。为什么?因为我想检查,100% 确定 fetch() 在失败时抛出异常,而不仅仅是 return FALSE 在失败时抛出异常。

如果是这样的话,那么我确实会考虑由 fetch() 编辑的 FALSE return,因为在数据库中找不到任何记录 table.

所以,我的问题是:您知道模拟 fetch() 方法失败情况的方法吗?

谢谢。

P.S.:我的问题的答案将帮助我找到另一个问题的答案:


编辑 1:

我还准备了一个例子,来展示我是如何处理异常的。这是关于一个简单的 sql 查询,从 users table:

中获取用户
<?php

// Activate error reporting.
error_reporting(E_ALL);
ini_set('display_errors', 1);

try {

    // Create a PDO instance as db connection to a MySQL db.
    $connection = new PDO(
            'mysql:host=localhost;port=3306;dbname=mydb;charset=utf8'
            , 'myuser'
            , 'mypass'
            , array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_EMULATE_PREPARES => FALSE,
        PDO::ATTR_PERSISTENT => TRUE
            )
    );

    // Define the sql statement.
    $sql = 'SELECT * FROM users WHERE name = :name';

    /*
     * Prepare and validate the sql statement.
     * 
     * --------------------------------------------------------------------------------
     * If the database server cannot successfully prepare the statement, PDO::prepare() 
     * returns FALSE or emits PDOException (depending on error handling settings).
     * --------------------------------------------------------------------------------
     */
    $statement = $connection->prepare($sql);

    if (!$statement) {
        throw new UnexpectedValueException('The sql statement could not be prepared!');
    }

    // Bind the input parameter to the prepared statement.
    $bound = $statement->bindValue(':name', 'Sarah', PDO::PARAM_STR);

    // Validate the binding of the input parameter.
    if (!$bound) {
        throw new UnexpectedValueException('An input parameter can not be bound!');
    }

    /*
     * Execute the prepared statement.
     * 
     * ------------------------------------------------------------------
     * PDOStatement::execute returns TRUE on success or FALSE on failure.
     * ------------------------------------------------------------------
     */
    $executed = $statement->execute();

    if (!$executed) {
        throw new UnexpectedValueException('The prepared statement can not be executed!');
    }

    /*
     * Fetch and validate the result set.
     * 
     * =========================================================
     * Note:
     * =========================================================
     * PDOStatement::fetch returns FALSE not only on failure,
     * but ALSO when no record is found!
     * 
     * Instead, PDOStatement::fetchAll returns FALSE on failure,
     * but an empty array if no record is found. This is the
     * natural, desired behaviour.
     * =========================================================
     */
    $resultset = $statement->fetch(PDO::FETCH_ASSOC);

    if ($resultset === FALSE) {
        throw new UnexpectedValueException('Fetching data failed!');
    }

    // Display the result set.
    var_dump($resultset);
    echo '<pre>' . print_r($resultset, TRUE) . '</pre>';

    // Close connection.
    $connection = NULL;
} catch (PDOException $exc) {
    echo '<pre>' . print_r($exc, TRUE) . '</pre>';
    exit();
} catch (Exception $exc) {
    echo '<pre>' . print_r($exc, TRUE) . '</pre>';
    exit();
}

我使用了以下创建 table 语法:

DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=124 DEFAULT CHARSET=utf8;

和以下 table 值:

INSERT INTO `users` (`id`, `name`)
VALUES
    (1,'Sarah'),
    (2,'John');

因此,table 看起来像这样:

id  name
--------
1   Sarah
2   John

除非循环需要 PDOStatement,否则可以在 method/function 中使用 fetchAll 来获得所需的结果。只需执行 fetchAll 而不是 fetch,检查是否为 false,并根据需要检查 return。在这种情况下,您的查询应确保只有 1 行是 returned.

像这样;

function fetch(\PDOStatement $pdo_stmt)
{
    // use fetchAll as an empty result set is returned by PDO as false using fetch()
    $result = $pdo_stmt->fetchAll(\PDO::FETCH_ASSOC);
    if ($result !== false) {
        return !empty($result) ? $result[0] : [];
    }
    return false;
}

function fetchColumn(\PDOStatement $pdo_stmt)
{
    // this will return false if no rows or not found...
    $result = $pdo_stmt->fetchColumn();
    if (empty($pdo_stmt->errorInfo())) {
        return $result !== false ? $result : null;
    }
    return false;
}

请注意 fetchColumn 也有类似的问题。

以上会return;

  • 查询失败时为 false
  • 如果未找到任何行,则获取一个空数组
  • 如果找到合适的结果集进行提取
  • 如果找不到,则为 fetchColumn 为 null
  • fetchColumn 的列值(如果找到)

扫描你的代码示例,你可以这样实现;

$sql  = 'SELECT * FROM users WHERE name = :name';
$stmt = $connection->prepare($sql);
$stmt->bindValue(':name', 'Sarah');
$executed = $stmt->execute();

if (!$executed) {
    throw new UnexpectedValueException('The prepared statement can not be executed!');
}

$result_set = fetch($stmt);
if ($result_set === false) {
    throw new UnexpectedValueException('Fetching data failed!');
}
// handle result set

您可以做更多的事情来改进上述内容,但希望您能理解。

PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTIONfetch如果有错误总是会抛出异常。您可以在 catch 块中处理它们,而不管它返回什么。因此,如果您没有捕获异常并且它返回 false,您可以放心地假设它是由于一个空集造成的。这是处理 PDO 错误的一种非常有效的方法。要回答您的问题,有很多方法可以模拟错误。最基本的是不正确的查询语法。您还可以尝试绑定一个不存在的参数,绑定错误数量的参数等。您使用此方案的时间越长,您看到的 errors/exceptions 类型就会越多。它非常有效,因为每个错误都包含一条详细消息以帮助您调试它。

最后,我找到了一个案例,它允许我测试,如果 PDOStatement::fetch 确实会在失败时抛出异常。

学分:

文章Taking advantage of PDO’s fetch modes就呈现了这样一种情况。它基于使用 PDOStatement::fetchAllPDO::FETCH_KEY_PAIR 常量作为参数传递。

测试:

所以,我运行自己测试一下。但是我改用了 PDOStatement::fetch 方法。根据定义,PDO::FETCH_KEY_PAIR 常量要求数据源 table 仅包含两列。在我的测试中,我定义了三个 table 列。 PDOStatement::fetch 已将这种情况识别为失败并抛出异常:

SQLSTATE[HY000]: General error: PDO::FETCH_KEY_PAIR fetch mode requires the result set to contain extactly 2 columns.

结论:

  • PDOStatement::fetchreturnsFALSE,如果没有找到记录。
  • PDOStatement::fetch 抛出 - 实际上 - 失败时的异常。

备注:

  • 相反,如果没有找到记录,PDOStatement::fetchAll returns 一个空数组。
  • PDO::FETCH_KEY_PAIR 常量未记录在 PDOStatement::fetch 官方页面上。

P.S:

我要感谢所有试图帮助我找到问题答案的用户。我很感激你!


用于测试的代码:

<?php

// Activate error reporting.
error_reporting(E_ALL);
ini_set('display_errors', 1);

try {

    // Create a PDO instance as db connection to a MySQL db.
    $connection = new PDO(
            'mysql:host=localhost;port=3306;dbname=tests;charset=utf8'
            , 'root'
            , 'root'
            , array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_EMULATE_PREPARES => FALSE,
        PDO::ATTR_PERSISTENT => TRUE
            )
    );

    // Define the sql statement.
    $sql = 'SELECT * FROM users WHERE name = :name';

    /*
     * Prepare the sql statement.
     * 
     * --------------------------------------------------------------------------------
     * If the database server cannot successfully prepare the statement, PDO::prepare() 
     * returns FALSE or emits PDOException (depending on error handling settings).
     * --------------------------------------------------------------------------------
     */
    $statement = $connection->prepare($sql);

    // Validate the preparation of the sql statement.
    if (!$statement) {
        throw new UnexpectedValueException('The sql statement could not be prepared!');
    }

    // Bind the input parameter to the prepared statement.
    $bound = $statement->bindValue(':name', 'Sarah', PDO::PARAM_STR);

    // Validate the binding of the input parameter.
    if (!$bound) {
        throw new UnexpectedValueException('An input parameter can not be bound!');
    }

    /*
     * Execute the prepared statement.
     * 
     * ------------------------------------------------------------------
     * PDOStatement::execute returns TRUE on success or FALSE on failure.
     * ------------------------------------------------------------------
     */
    $executed = $statement->execute();

    // Validate the execution of the prepared statement.
    if (!$executed) {
        throw new UnexpectedValueException('The prepared statement can not be executed!');
    }

    // Fetch the result set.
    $resultset = $statement->fetch(PDO::FETCH_KEY_PAIR);

    // If no records found, define the result set as an empty array.
    if ($resultset === FALSE) {
        $resultset = [];
    }

    // Display the result set.
    var_dump($resultset);

    // Close connection.
    $connection = NULL;
} catch (PDOException $exc) {
    echo '<pre>' . print_r($exc->getMessage(), TRUE) . '</pre>';
    exit();
} catch (Exception $exc) {
    echo '<pre>' . print_r($exc->getMessage(), TRUE) . '</pre>';
    exit();
}

创建 table 语法:

DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL,
  `phone` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

插入值语法:

INSERT INTO `users` (`id`, `name`, `phone`)
VALUES
    (1,'Sarah','12345'),
    (2,'John','67890');

Table 值:

id  name    phone
-----------------
1   Sarah   12345
2   John    67890