Si une erreur se produit au cours de la requête, mysqli_query() renvoie FALSE. Nous devons tester cette valeur de retour pour agir en conséquence, le plus souvent par l'arrêt du traitement en cours et l'affichage d'un message ou le déroutement vers une autre page.

Une erreur peut avoir généralement 3 causes :

- la requête contient une faute dans le nom d'un champ, d'une table ou d'une commande SQL (par exemple liPage à la place de liPages, lives à la place de livres, SELET à la place de SELECT, etc)
- la requête essaye de faire une opération "interdite" (par exemple insérer un enregistrement avec une clé primaire déjà existante)
- la requête contient des caractères mal protégés qui provoquent une erreur de syntaxe.

L'exemple suivant essaye de faire une insertion dans la table auteurs avec un clé primaire déjà existante.

Exemple : erreur de requête
<?php ob_start(); require('bib_params.php'); require('bib_fonctions.php'); htmlDebut('Erreur dans un insert'); $bd = bdConnecter(); //-- Requête ---------------------------------------- $sql = 'INSERT INTO auteurs SET auID = 1, auNom = "Piat", auPrenom = "François", auPays = "FR", auBiographie = ""'; $r = mysqli_query($bd, $sql); echo 'La mise à jour à été effectuée correctement.'; //-- Déconnexion ------------------------------------ mysqli_close($bd); htmlFin(); ?>

Comme nous ne gérons pas les erreurs, le message 'La mise à jour à été effectuée correctement.' s'affiche et laisse croire que la requête INSERT s'est correctement déroulée alors que ce n'est pas le cas.

Un autre exemple avec une requête SELECT et un mauvais nom de table.

Exemple : erreur de requête
<?php ob_start(); require('bib_params.php'); require('bib_fonctions.php'); htmlDebut('Erreur dans un select'); $bd = bdConnecter(); //-- Requête ---------------------------------------- $sql = 'SELECT * FROM lives WHERE liID = 10'; $r = mysqli_query($bd, $sql); //-- Traitement ------------------------------------- $enr = mysqli_fetch_assoc($r); infoTableau($enr); // Libération de la mémoire associée au résultat de la requête mysqli_free_result($r); //-- Déconnexion ------------------------------------ mysqli_close($bd); htmlFin(); ?>

On a ici l'affichage d'un message signalant une erreur lors de l'appel de la fonction mysqli_fetch_assoc() à laquelle nous avons passé un booléen alors qu'elle attend un objet mysqli_result . L'erreur provient en réalité de mysqli_query(), mais comme nous ne testons pas la valeur de retour nous avons des erreurs en cascade.

Il est primordial de tester la valeur de retour de la fonction mysqli_query() avant de continuer quelque traitement que ce soit.

Nous gérerons le plus simplement possible les erreurs de requêtes avec l'opérateur or. Avec cet opérateur or PHP permet d'appeler une seconde fonction uniquement si le résultat d'une première est évalué à FALSE. Les appels à mysqli_query() devront toujours être effectués comme suit :
$r = mysqli_query($bd, $sql) or bdErreur($bd, $sql);

En effet, l'opérateur or, comme les autres opérateurs logiques and, && et || est un opérateur "paresseux", ce qui signifie que ces opérateurs n'évaluent pas leur deuxième opérande quand l'évaluation du premier opérande suffit pour connaître le résultat de l'opération logique. Donc:

- quand l'appel de mysqli_query() réussit, elle renvoie un objet de type un objet mysqli_result non vide dans le cas d'une requête SELECT, ou TRUE dans le cas des requêtes INSERT, UPDATE ou DELETE. Dans les 2 cas, après un éventuel transtypage, cette valeur de retour est évaluée à TRUE. Le résultat de l'opérateur logique or est alors forcément égal à TRUE (comme TRUE or X = TRUE quelque soit X); et donc la fonction bdErreur() n'est pas appelée;
- quand l'exécution de la requête SQL échoue, mysqli_query() renvoie FALSE. Dans ce cas, l'opérateur logique or a besoin d'évaluer son deuxième opérande pour connaître le résultat de l'opération logique, donc la fonction bdErreur() est appelée.
- l'instruction

      $r = mysqli_query($bd, $sql) or bdErreur($bd, $sql);

  est donc strictement équivalente à :

      $r = mysqli_query($bd, $sql);
      if (!$r){
      	bdErreur($bd, $sql);
      }

  La première instruction est simplement plus concise.

Dans la gestion des erreurs nous distinguerons 2 cas : le site est en phase de développement (message explicatif pour le débuggage) ou le site est en phase de production ( message sibyllin et tracking dans un fichier d'erreurs).

Deux fonctions nous permettent d'en savoir un peu plus sur les erreurs MySQL :

- mysqli_errno() renvoie le numéro de la dernière erreur MySQL,
- mysqli_error() renvoie un texte d'erreur correspondant au numéro d'erreur.

Il est particulièrement important que l'on ait le texte complet de la requête SQL qui provoque une erreur car :

- le texte fourni par mysqli_error() n'est parfois pas adapté et toujours tronqué,
- 90% des requêtes sont faites avec des éléments qui proviennent de saisies utilisateur (directement ou indirectement) et leur composition peut servir à la résolution du problème.

La fonction de gestion d'erreurs sera placée dans notre bibliothèque de fonctions habituelle (bib_fonctions.php)

    //_____________________________________________________________
    /**
      * Gestion d'une erreur de requête à la base de données.
      *
      * @param resource	$bd		Connecteur sur la bd ouverte
      * @param string	$sql	requête SQL provoquant l'erreur
      */
    function bdErreur($bd, $sql) {
        $errNum = mysqli_errno($bd);
        $errTxt = mysqli_error($bd);

        // Collecte des informations facilitant le debugage
        $msg =  '<h4>Erreur de requête</h4>'
                ."<b>Erreur mysql :</b> $errNum"
                ."<br> $errTxt"
                ."<br><br><b>Requête :</b><br><pre>$sql</pre>"
                .'<br><br><b>Pile des appels de fonction :</b>';

    	$tdStyle = 'style="border: 1px solid black;padding: 4px 10px"';

        // Récupération de la pile des appels de fonction
        $msg .= '<table style="border-collapse: collapse">'
                ."<tr><td $tdStyle>Fonction</td>"
    			."<td $tdStyle>Appelée ligne</td>"
                ."<td $tdStyle>Fichier</td></tr>";

        $appels = debug_backtrace();
        for ($i = 0, $iMax = count($appels); $i < $iMax; $i++) {
            $msg .= "<tr style='text-align: center'><td $tdStyle>"
                    .$appels[$i]['function']."</td><td $tdStyle>"
                    .$appels[$i]['line']."</td><td $tdStyle>"
                    .$appels[$i]['file'].'</td></tr>';
        }

        $msg .= '</table>';

        bdErreurExit($msg);
    }

    //_____________________________________________________________
    /**
     * Arrêt du script si erreur base de données.
     * Affichage d'un message d'erreur si on est en phase de
     * développement, sinon stockage dans un fichier log.
     *
     * @param string	$msg	Message affiché ou stocké.
     */
    function bdErreurExit($msg) {
    	ob_end_clean();		// Supression de tout ce qui
    						// a pu être déja généré

    	// Si on est en phase de développement, on affiche le message
    	if (IS_DEV) {
    		htmlDebut('Erreur base de données');
    		echo $msg;
    		htmlFin();
    		exit();
    	}

    	// Si on est en phase de production on stocke les
    	// informations de débuggage dans un fichier d'erreurs
    	// et on affiche un message sibyllin.
    	$buffer = date('d/m/Y H:i:s')."\n$msg\n";
    	error_log($buffer, 3, 'erreurs_bd.txt');

    	htmlDebut('Maintenance en cours');
    	// Gros mensonge
    	echo 'Notre site est momentanément indisponible ',
    		'pour cause de maintenance. Merci de ré-essayer ',
    		'dans quelques instants.';
    	htmlFin();
    	exit();
    }

La seule véritable nouveauté par rapport à notre fonction de gestion des erreurs de connexion est l'utilisation de la fonction debug_backtrace() pour générer la pile des fonctions appelées qui va nous permettre de suivre à la trace le traitement provoquant l'erreur.

Dans cet exemple l'erreur provoquée résulte d'appel d'appel d'appel de fonctions pour qu'on voie bien l'utilité de debug_backtrace().

Exemple : erreur de requête
<?php ob_start(); require('bib_params.php'); require('bib_fonctions.php'); htmlDebut('Erreur dans un select'); $bd = bdConnecter(); fonctionUn(); //-- Déconnexion ------------------------------------ mysqli_close($bd); htmlFin(); //------------------------------------------ function fonctionUn() { deuxiemeFonction(); } //------------------------------------------ function deuxiemeFonction() { trois(); } //------------------------------------------ function trois() { global $bd; //-- Requête ---------------------------------------- $sql = 'SELECT * FROM lives WHERE liID = 10'; $r = mysqli_query($bd, $sql) or bdErreur($bd, $sql); //-- Traitement ------------------------------------- $enr = mysqli_fetch_assoc($r); //- Libération de la mémoire associée au résultat //- de la requête mysqli_free_result($r); infoTableau($enr); } ?>

Avec le tableau affichant la pile des appels de fonction, on voit bien tout le traitement qui a causé l'erreur détectée en ligne 30 de notre code.