Simulate a PDO fetch failure situation

前端 未结 3 1189
無奈伤痛
無奈伤痛 2021-01-20 05:27

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

相关标签:
3条回答
  • 2021-01-20 05:35

    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
    
    0 讨论(0)
  • 2021-01-20 05:48

    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.

    0 讨论(0)
  • 2021-01-20 05:53

    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.

    0 讨论(0)
提交回复
热议问题