How to launch my own exceptions with PDO?

Asked

Viewed 1,071 times

8

When running the line below, in the constructor of my database access class:

$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

PDO will report errors by releasing type exceptions PDOException. Is there any setting so I can make you launch my DatabaseException?

I didn’t want to have to try catch() with a throw new DatabaseException() in all that is method that has in this my access class to the bank, that already it extends another class with the concrete methods, that is of a framework.

EDIT: My Database class, whose methods may launch the DatabaseException, extends the DAO catfan/medoo which is loaded via Composer. Therefore, I should not touch the structure of the original class, since it will always be updated. So I didn’t want to create a strong dependency as a proxy.

  • I don’t have a definitive answer yet, but looking at the sources of PDO and of PEAR::raiseError - in particular on the line $ec = $this->_error_class; - I suspect it is possible yes... Tomorrow, if no one finds a solution, I will investigate again.

1 answer

3


I did some research and analyzed the documentation officer. It seems not possible to replace the exception PDOException by a custom via some simple configuration.

However, I have thought of some ways around the problem. Maybe some of it will fit your case.

Capture the exception globally

A simplistic and limited solution, if the exception can be handled globally, would be to use the function set_exception_handler() to capture exceptions not treated by blocks try/catch.

The example below captures the exceptions and checks if it is of the type PDOException. If it is, it displays a message and allows the program to continue running. Otherwise it relaunches the exception.

function pdoExceptionHandler($e) {
    if ($e instanceof PDOException) {
        echo 'Erro PDO Capturado!';
    } else {
        throw $e;
    }
}
set_exception_handler("pdoExceptionHandler");

See the functional example on codepad.

This solution is limited because the function Handler is executed only if the exception is not captured anywhere by a catch.

Encapsulate the PDO

Another approach would be not to use PDO classes directly, but to create Wrappers to abstract its functionalities.

Encapsulation with inheritance

The first approach is to create classes that inherit from the original PDO and overwrite the required methods by adding the treatment try/catch and relaunching the custom exception. So you do the treatment once and reuse in all bank access you need.

In this question from the SOEN, I found an example similar to this, that is, a class abstract the use of PDO. Note that the questioner states that there is a problem when closing the connection with this class. Unfortunately I lack an environment to test and validate how it works. If you want to use it as a basis to develop yours, follow the code:

class Database extends PDO {
    private $driver = "mysql";
    private $host = "localhost";
    private $dbname = "dbname";
    private $user = "user";
    private $pass = "pass";
    private $connect = false;
    private $error = "";
    private $stmt = "";

    public function __construct() {
        $options = array(
            PDO::ATTR_PERSISTENT => true, 
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        );

        try {
            parent::__construct($this->driver.":host=".$this->host.";dbname=".$this->dbname, $this->user, $this->pass, $options);
        } catch (PDOException $e) {
            $this->error = $e->getMessage();
        }
        $this->connect = true;
    }

    public function run($statement, $bind = array()) {
        try {
            $this->stmt = $this->prepare($statement);
            $this->stmt->execute($bind);
        } catch (Exception $e) {
            throw $e;
        }

    }

    public function fetchAssoc() {
        return $this->stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    public function rowCount() {
        return $this->stmt->rowCount();
    }

    public function getErrorMessage() {
        return $this->error;
    }

    public function isOpen() {
        return $this->connect;
    }

    public function close() {
        //$this->connect = false;
    }

    public function __destruct() {
        $this->connect = false;
    }
}

Encapsulation with a proxy

Another approach would be to create a class that would function as a proxy for the real PDO. It would have an attribute that references the PDO and methods with the same signatures as the PDO, which delegate the execution to the PDO, but treat the exception properly.

A technique that would help in this last approach not to need to create all methods manually would be to use the triggers __call and __callStatic (see documentation). With them you can delegate normal and static calls to methods without creating each method, dynamically and doing the treatment in one point.

When a class has a method __call($name, $arguments), for example, and you call any method in that class, even if the method is not declared, PHP will run the __call passing the name of the called method ($name) and the parameters in an array ($arguments). It’s a very nice feature of PHP!

I did a basic implementation

class MyPDO {
    private $pdo = null;
    function __construct($url, $user, $pw) {
        $this->pdo = new PDO($url, $user, $pw);
    }
    public function __call($name, $arguments) {
        try {
            call_user_func_array(array($this->pdo, $name), $arguments);
        } catch (PDOException $ex) {
            throw new DataBaseException('database error');
        }
    }
}

See a functional example here.

Encapsulating with a library

I found a project called php-Pdo-wrapper-class which aims to facilitate a little the use of PDO. In addition to bringing some useful methods, it has a method called setErrorCallbackFunction() that might solve your problem without you needing to create your own solution.

  • This would be part of a framework that I build, but I don’t see a single way out other than rewriting all the methods, which I still cherish for the practicality of this application. The Proxy standard was the one that came closest to working.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.