web-dev-qa-db-fra.com

Utilisation de PDO try-catch dans les fonctions

Je pense utiliser PDO dans toutes mes futures webapp. Actuellement (en utilisant ce que j'ai appris de SO jusqu'à présent), ce que j'ai sur mon site pour gérer la connexion à la base de données est une classe Singleton comme celle-ci:

class DB {

    private static $instance = NULL;
    private static $dsn      = "mysql:Host=localhost;dbname=mydatabase;";
    private static $db_user  = 'root';
    private static $db_pass  = '0O0ooIl1';

    private function __construct() 
    {

    }
    private function __clone()
    {

    }   
    public static function getInstance() {

        if (!self::$instance)
        {           
            self::$instance = new PDO(self::$dsn, self::$db_user, self::$db_pass);
            self::$instance-> setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        }
        return self::$instance;
    }
}

et un autre fichier (functions.php) avec des fonctions spécifiques au contenu ressemblant exactement à celui-ci:

function get_recent_activities ()
{    
    try
    {    
        $db = DB::getInstance();
        // --prepare and execute query here, fetch the result--
        return $my_list_of_recent_activities;
    }
    catch (PDOException $e)
    {
        return "some fail-messages";
    }
}
...

ce qui signifie que je dois répéter le try .. catch participe à toutes les fonctions.

Mes questions sont:

  1. Comment devrais-je rendre cela plus efficace? (par exemple, ne pas avoir à répéter try..catch dans toutes les fonctions, mais toujours capable de renvoyer des "messages d'échec" différents sur chacune)
  2. Est-ce déjà une bonne pratique? Je suis encore nouveau chez PDO et OOP (encore beaucoup plus à apprendre), donc (pour l'instant), je ne vois pas vraiment d'inconvénients ou de choses qui peuvent être améliorées là-dedans .

Je suis désolé si cela ne semble pas clair ou trop long. Merci d'avance.

30
andyk

Votre implémentation est très bien, et cela fonctionnera parfaitement bien dans la plupart des cas.

Il n'est pas nécessaire de placer chaque requête dans un bloc try/catch, et en fait, dans la plupart des cas, vous ne le souhaitez pas. La raison en est que si une requête génère une exception, elle est le résultat d'un problème fatal, comme une erreur de syntaxe ou un problème de base de données, et ce ne sont pas des problèmes que vous devez prendre en compte avec chaque requête que vous effectuez.

Par exemple:

try {
    $rs = $db->prepare('SELECT * FROM foo');
    $rs->execute();
    $foo = $rs->fetchAll();
} catch (Exception $e) {
    die("Oh noes! There's an error in the query!");
}

La requête ici fonctionnera correctement ou ne fonctionnera pas du tout. Les circonstances où cela ne fonctionnerait pas du tout ne devraient jamais se produire avec une régularité sur un système de production, donc ce ne sont pas des conditions que vous devriez vérifier ici. Cela est en fait contre-productif, car vos utilisateurs obtiennent une erreur qui ne changera jamais, et vous ne recevez pas de message d'exception qui vous alerterait du problème.

Au lieu de cela, écrivez simplement ceci:

$rs = $db->prepare('SELECT * FROM foo');
$rs->execute();
$foo = $rs->fetchAll();

En général, le seul moment où vous voudrez intercepter et gérer une exception de requête est lorsque vous voulez faire autre chose si la requête échoue. Par exemple:

// We're handling a file upload here.
try {
    $rs = $db->prepare('INSERT INTO files (fileID, filename) VALUES (?, ?)');
    $rs->execute(array(1234, '/var/tmp/file1234.txt'));
} catch (Exception $e) {
    unlink('/var/tmp/file1234.txt');
    throw $e;
}

Vous souhaiterez écrire un gestionnaire d'exceptions simple qui enregistre ou vous informe des erreurs de base de données qui se produisent dans votre environnement de production et affiche un message d'erreur convivial à vos utilisateurs au lieu de la trace d'exception. Voir http://www.php.net/set-exception-handler pour plus d'informations sur la façon de procéder.

40
pd.

Voici quelques mises en garde:

  • Ce code est écrit pour prendre en compte plusieurs problèmes hérités tels que la journalisation de la base de données et la gestion de la configuration de la base de données.
  • Je vous recommande fortement de regarder une solution existante avant de construire la vôtre. Beaucoup de gens pensent au début qu’ils ne veulent pas utiliser un framework ou une bibliothèque existante parce qu’ils sont trop gros, nécessitent trop de temps pour apprendre, etc., mais après avoir été une de ces personnes, je Je ne peux pas affirmer avec force que je quitte mon cadre personnalisé et mes classes d'encapsuleur pour passer à un cadre. Je cherche à déménager à Zend, mais il existe un certain nombre d'excellents choix disponibles.

Oh, je dois souligner que ce point illustre comment on pourrait encapsuler une seule fonction pour gérer l'ensemble de la gestion des exceptions pour vos requêtes. Je n'écris pas de blocs try catch presque partout ailleurs maintenant parce que la trace de pile de la requête me donne toutes les informations dont j'ai besoin pour déboguer le problème et le résoudre.

Voici mon implémentation actuelle de classe wrapper PDO:

class DB extends PDO 
{
    // Allows implementation of the singleton pattern -- ndg 5/24/2008
    private static $instance;

    // Public static variables for configuring the DB class for a particular database -- ndg 6/16/2008
    public static $error_table;
    public static $Host_name;
    public static $db_name;
    public static $username;
    public static $password;
    public static $driver_options;
    public static $db_config_path;



    function __construct($dsn="", $username="", $password="", $driver_options=array()) 
    {
        if(isset(self::$db_config_path))
        {
            try 
            {
                if(!require_once self::$db_config_path)
                {
                    throw new error('Failed to require file: ' . self::$db_config_path); 
                }
            } 
            catch(error $e) 
            {
                $e->emailAdmin();
            }
        }
        elseif(isset($_ENV['DB']))
        {
            self::$db_config_path = 'config.db.php';

            try 
            {
                if(!require_once self::$db_config_path)
                {
                    throw new error('Failed to require file: ' . self::$db_config_path); 
                }
            } 
            catch(error $e) 
            {
                $e->emailAdmin();
            }
        }

        parent::__construct("mysql:Host=" . self::$Host_name . ";dbname=" .self::$db_name, self::$username, self::$password, self::$driver_options);
        $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('QueryStatement', array($this)));

        if(!isset(self::$error_table))
        {
            self::$error_table = 'errorlog_rtab';
        }
    }

    /**
     * Return a DB Connection Object
     *
     * @return DB
     */
    public static function connect()
    {

        // New PDO Connection to be used in NEW development and MAINTENANCE development
        try 
        {
            if(!isset(self::$instance))
            {   
                if(!self::$instance =  new DB())
                {
                    throw new error('PDO DB Connection failed with error: ' . self::errorInfo());
                }
            }

            return self::$instance;
        }
        catch(error $e)
        {
            $e->printErrMsg();
        }
    }

    /**
     * Returns a QueryBuilder object which can be used to build dynamic queries
     *
     * @return QueryBuilder
     * 
     */
    public function createQuery()
    {
        return new QueryBuilder();
    }

    public function executeStatement($statement, $params = null, $FETCH_MODE = null)
    {
        if($FETCH_MODE == 'scalar')
        {
            return $this->executeScalar($statement, $params);   
        }


        try {
            try {
                if(!empty($params))
                {
                    $stmt = $this->prepare($statement);
                    $stmt->execute($params);
                }
                else 
                {
                    $stmt = $this->query($statement);
                }
            }
            catch(PDOException $pdo_error)
            {
                throw new error("Failed to execute query:\n" . $statement . "\nUsing Parameters:\n" . print_r($params, true) . "\nWith Error:\n" . $pdo_error->getMessage());
            }
        }
        catch(error $e)
        {
            $this->logDBError($e);
            $e->emailAdmin();
            return false;
        }

        try 
        {
            if($FETCH_MODE == 'all')
            {
                $tmp =  $stmt->fetchAll();
            }
            elseif($FETCH_MODE == 'column')
            {
                $arr = $stmt->fetchAll();

                foreach($arr as $key => $val)
                {
                    foreach($val as $var => $value)
                    {
                        $tmp[] = $value;
                    }
                }           
            }
            elseif($FETCH_MODE == 'row') 
            {
                $tmp =  $stmt->fetch();
            }
            elseif(empty($FETCH_MODE))
            {
                return true;
            }
        }
        catch(PDOException $pdoError)
        {
            return true;
        }

        $stmt->closeCursor();

        return $tmp;

    }

    public function executeScalar($statement, $params = null)
    {
        $stmt = $this->prepare($statement);

        if(!empty($this->bound_params) && empty($params))
        {
            $params = $this->bound_params;
        }

        try {
            try {
                if(!empty($params))
                {
                    $stmt->execute($params);
                }
                else 
                {
                        $stmt = $this->query($statement);
                }
            }
            catch(PDOException $pdo_error)
            {
                throw new error("Failed to execute query:\n" . $statement . "\nUsing Parameters:\n" . print_r($params, true) . "\nWith Error:\n" . $pdo_error->getMessage());
            }
        }
        catch(error $e)
        {
            $this->logDBError($e);
            $e->emailAdmin();
        }

        $count = $stmt->fetchColumn();

        $stmt->closeCursor();

        //echo $count;
        return $count;      
    }

    protected function logDBError($e)
    {
        $error = $e->getErrorReport();

        $sql = "
        INSERT INTO " . self::$error_table . " (message, time_date) 
        VALUES (:error, NOW())";

        $this->executeStatement($sql, array(':error' => $error));
    }
}

class QueryStatement extends PDOStatement 
{
    public $conn;

    protected function __construct() 
    {
        $this->conn = DB::connect();
        $this->setFetchMode(PDO::FETCH_ASSOC);
    }

    public function execute($bound_params = null)
    {
        return parent::execute($bound_params);          
    }
}
3
Noah Goodrich