Niveau : Confirmé

Gérer les erreurs MySQL en PHP sans or die

Utiliser or die(...) après une commande MySQL est devenu une mauvaise habitude pour gérer les erreurs. Cette pratique est encouragée par la documentation officielle PHP, dans ses exemples de scripts illustrant les fonctions, mais également dans les forums où elle est banalisée jusqu'à devenir un "standard" de programmation.

Ce tutoriel vous montrera les limites de cette pratique et fournira une méthodologie pour coder propre sans pour autant alourdir les scripts.

Tutoriel par (Ingénieur)
Créé le , mis à jour le (128106 lectures)
Tags : php, bonnes pratiques, développement, mysql, erreur, pdo

MySQL logo

Sommaire

Know your enemy : or et die

Avant toute chose, il est important de comprendre pourquoi or die(...) est utilisé à mauvais escient. Pour ce faire, il faut se pencher sur chaque instruction : or et die.

L'opérateur or

or est l'opérateur logique "ou", dont la table est :

a b a or b
0 0 0
0 1 1
1 0 1
1 1 1

On déduit de cette table la règle suivante : ∀x ∈ {0,1}, 1 or x = 1.

Dans un script, les expressions d'une opération or sont évaluées de gauche à droite. Par souci d'optimisation, et conformément à la règle précédente, la première expression "vraie" valide le résultat, les expressions suivantes n'ont plus besoin d'être évaluées.

La fonction die

die est un alias de exit : c'est une fonction native de PHP qui permet de stopper l'exécution du script. Si une chaîne de caractères est passée en argument, elle est affichée avant l'arrêt. Si un entier est passé en argument, il peut être utilisé comme statut de sortie (0 signifiant que le programme s'est terminé avec succès).

Utilisation de or die dans la gestion d'erreur

Le règle déterminant le résultat d'un ou logique est appliquée lors des connexions au serveur MySQL. Soit la portion de code suivante :

$link = mysql_connect(...) or
    die('Impossible de se connecter au serveur MySQL');

Si la connexion réussit, mysql_connect retourne un identifiant de connexion. Cet identifiant n'étant pas considéré comme "faux", il est converti en booléen true qui valide le résultat, die n'est donc pas exécutée.

Si la connexion échoue, mysql_connect retourne false, die est donc exécutée pour déterminer le résultat. Il en résulte donc l'affichage du message d'erreur et l'arrêt brutal du script.

À noter : L'opérateur = est prioritaire sur or, par conséquent $link se verra attribuer la valeur retournée par mysql_connect, et non le résultat de l'opération logique.

Conséquences

Lorsque la connexion au serveur échoue (serveur indisponible, erreur de paramétrage…), trois choses se produisent :

  1. Affichage d'un message d'erreur de la part de PHP, du type :
    Warning:
    mysql_connect() [function.mysql-connect]:
    Can't connect to MySQL server on 'localhost' (10061) in fichier php on line numéro de ligne
  2. Affichage du message "Impossible de se connecter au serveur MySQL"
  3. Arrêt du script

Les points 1 et 3 sont problématiques. Le message d'erreur de PHP :

  • est disgrâcieux,
  • fournit des informations "confidentielles" (nom du serveur, nom et chemin du fichier),
  • produit un affichage qui empêche une éventuelle redirection / création de cookie / etc. (tout ce qui nécessite les entêtes HTTP);

L'arrêt du script :

  • invalide la page HTML (si du code a déjà été produit, manqueront les balises de fermeture des éléments précédemment ouverts; si aucun code n'a été produit, la "page" ne déclare même pas de doctype)
  • dénote un manque de professionnalisme en fournissant une page "baclée". Quand la page se limite au message d'erreur brut, l'identité visuelle du site est perdue.

Ajoutons à cela qu'un site proposant un service se doit d'être courtois avec ses visiteurs, de s'excuser platement pour la gêne occasionnée et de s'exprimer en termes compréhensibles. Évitons donc les messages allègrement copiés-collés du type "Impossible de se connecter au serveur MySQL" (demandez donc à votre grand-mère si elle connait MySQL…).

Oubliez également les propositions visant à contacter l'administrateur : vos internautes ont autre chose à faire, et vous êtes déjà (ou serez bientôt) prévenus automatiquement ou par consultation des logs.

Solutions

Tests avec if/else

En programmation procédurale avec le module mysql, il suffit de tester le retour des fonctions qui sera false en cas d'échec.

$link = @mysql_connect('localhost', 'root', '');
if (!$link)
{
    // Traitement de l'erreur
}
else
{
  if (!@mysql_select_db('ma_base', $link)
  {
    // Traitement de l'erreur
  }
  else
  {
    $sql = 'SELECT champ1,champ2 FROM `ma_table`';
    $rs = @mysql_query($sql, $link);
    if (!$rs)
    {
      // Traitement de l'erreur
    }
    else
    {
      if (mysql_num_rows($rs) == 0)
      {
        // Traitement du cas où la requête n'a retourné aucun élément

      }
      else
      {
        // Exploitation des données
        while ($row = mysql_fetch_assoc($rs))
        {
          // ...
        }
      }
    }
  }
}

Par commodité, j'utilise ici l'opérateur de gestion d'erreur @ pour annuler l'affichage des messages d'erreur.

Pour plus d'informations sur la gestion des erreurs, voir le module correspondant sur php.net.

Utilisation de PDO

PDO est une interface pour accéder à une base de données depuis PHP à partir de la version 5.1. PDO définit un driver spécifique pour chaque format de base (MySQL, Oracle, SQLite, MS SQL, PostgreSQL...).

Pour initier une connexion à travers PDO, on utilise :

<?php
$dbh = new PDO('mysql:host='.$host.';dbname='.$database, $user, $pass);
?>

La gestion des erreurs éventuelles se fait via l'emploi de blocs try {...} catch {...}, dans lesquels on place en premier les actions à réaliser, puis la gestion des erreurs (appelées exceptions) si les instructions figurant dans try ont généré une exception.

<?php
$NomServeur = $_SERVER['SERVER_NAME'] ; 
$local = ( (substr($NomServeur, 0, 7) == '192.168') || ($NomServeur == 'localhost') || (substr($NomServeur, 0, 3) == '127') );

$host = $local ? 'localhost' : 'xxxxxx'; 
$user = $local ? 'root' : 'xxxxxx'; 
$pass = $local ? '' : 'xxxxxx'; 
$database = $local ? 'test' : 'xxxxxx'; 
$verbose = $local;

try {
    $dbh = new PDO('mysql:host='.$host.';dbname='.$database, $user, $pass);
    foreach($dbh->query('SELECT * from table') as $row) {
        // Traitement des résultats
    }
    $dbh = null;
} catch (PDOException $erreur) {
    if ($verbose) {
        echo 'Erreur : '.$erreur->getMessage();
    } else {
        echo 'Ce service est momentanément indisponible. Veuillez nous excuser pour la gêne occasionnée.';
    }
}
?>

Ceci va de surcroît permettre d'exploiter les transactions SQL.

Classe wrapper du module mysql

Il s'agit d'englober les fonctions du module mysql dans une classe personnalisée et de gérer soi-même le comportement en cas d'action illégale. Cette pratique devient obsolète avec l'apparition de PDO, mais peut se retrouver sur des projets anciens.

Conclusion

L'utilisation de or die(...) est très pratique pour s'assurer qu'une commande SQL s'est bien déroulée, mais elle implique une modification du code lors de la mise en ligne si l'on veut produire des pages HTML valides en toutes circonstances. Cette pratique est donc à proscrire au profit de tests systématiques ou d'utilisation de structures objets comme PDO.