Simulate a PDO fetch failure situation

Viewed 1846

Conform to the php docs, the PDO method fetch() returns the value FALSE both when no records are found AND on failure (e.g. when something goes wrong regarding the database access).

Let's suppose, I set the PHP error reporting system to throw exceptions on failure:

PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION

I need a case, a situation in which the fetch() method will throw an exception. Why? Because I want to check, to be 100% sure that fetch() throws an exception on failure, and doesn't just return FALSE on failure.

If that would be the case, then I would indeed consider the FALSE returned by fetch() as the result of not finding any records in the db table.

So, my question would be: Do you know a way to simulate a failure situation for the fetch() method?

Thank you.

P.S.: The answer to my question will help me find the answer for my other question: PHP PDO fetch returns FALSE when no records found AND on failure


Edit 1:

I also prepared an example, to show how I handle the exceptions. It's about a simple sql query, fetching a user from a 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();
}

I used the following create table syntax:

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;

and the following table values:

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

So, the table looks like this:

id  name
--------
1   Sarah
2   John
3 Answers

Finally, I found a case, which allowed me to test, if PDOStatement::fetch would indeed throw an exception on failure.

Credits:

The article Taking advantage of PDO’s fetch modes presents such a situation. It is based on the use of PDOStatement::fetchAll with PDO::FETCH_KEY_PAIR constant passed as argument.

Test:

So, I ran a test myself. But I used the PDOStatement::fetch method instead. Per definition, the PDO::FETCH_KEY_PAIR constant requires that the data source table contains only two columns. In my test I defined three table columns. PDOStatement::fetch has recognized this situation as a failure and had thrown an exception:

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

Conclusion:

  • PDOStatement::fetch returns FALSE, if no records are found.
  • PDOStatement::fetch throws - indeed - an exception in case of failure.

Notes:

  • Instead, PDOStatement::fetchAll returns an empty array, if no records are found.
  • The PDO::FETCH_KEY_PAIR constant is not documented on the PDOStatement::fetch official page.

P.S:

I want to thank to all the users who tried to help me finding the answer to my question. You have my appreciation!


The code used for testing:

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

Create table syntax:

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 values syntax:

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

Table values:

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

With PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION fetch will always throw an exception if there is an error. You can handle those in your catch block and it doesn't matter what it returned. Therefore, if you didn't catch an exception and it returned false you can safely assume that it was due to an empty set. This is a very effective way have handling PDO errors. To answer your question, there are many ways of simulating an error. The most basic is improper query syntax. You can also try to bind a parameter that isn't there, bind the wrong number of parameters etc. The longer you use this scheme the more types of errors/exceptions you will see. It is very effective since each error includes a detailed message to help you debug it.

Unless you need the PDOStatement for a loop, you can use fetchAll in a method/function to get the result you are looking for. Just do a fetchAll instead of a fetch, check for false, and return as necessary. In this case, your query should make sure only 1 row is returned.

Something like this;

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

Note that fetchColumn has a similar issue.

The above will return;

  • false on query failure
  • an empty array for fetch if no rows found
  • appropriate result set for fetch if found
  • null for fetchColumn if not found
  • column value for fetchColumn if found

Scanning your code sample, you could implement like so;

$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

There is more you can do to improve the above, but hopefully you get the idea.

Related