mysqli : la nouvelle technique pour travailler avec une base de données MySQL

Votre site Web devra fort probablement accéder à une base de données pour définir soit sa structure (ex : ses menus, ses configurations), soit son contenu (ex : le texte à afficher dans les pages Web, les usagers ayant droit de se connecter), soit les deux.

Il existe plusieurs techniques pour accéder à la base de données et y exécuter des requêtes. Avant PHP 5.5.0, plusieurs programmeurs utilisaient mysql_connect() et les autres fonctions de l'extension mysql (voir l'article « Travailler avec une base de données MySQL »).

Cette extensions est désormais obsolète. Les programmeurs avisés utilisent maintenant l'extension mysqli. Le i tient pour improved. Il s'agit donc d'une version améliorée de l'extension originale.

▼Publicité

Branchement à la base de données

Depuis PHP 5.5.0, les programmeurs PHP doivent utiliser la classe mysqli pour se brancher à la base de données.

Syntaxe PHP

$mysqli = new mysqli("addresse_serveur_mysql", "usager_mysql", "mot_de_passe", "base_de_donnees");

...   // attention : ce code est incomplet. Voir plus bas...

$mysqli->close();

Sur un site en développement, la connexion se fera comme suit :

PHP

$mysqli = new mysqli("localhost", "root", "", "garage");

...

$mysqli->close();

Si vous travaillez sur un site en ligne, la commande aura plutôt cette forme :

PHP

$mysqli = new mysqli("nomdomaine.com", "toto", "tata", "garage");

...

$mysqli->close();

Dans ce dernier exemple, l'usager toto serait un usager MySQL ayant des droits restreints. Ceci permet de limiter les dégâts en cas où un trou de sécurité serait découvert.

Refermer la connexion

Dans les extraits de code précédents, vous avez sans doute remarqué l'utilisation de $mysqli->close(). Cette méthode s'occupe de refermer la connexion afin de libérer les ressources le plus tôt possible. Si vous l'omettez, la connexion sera automatiquement refermée lorsque l'internaute refermera le site Web.

À ce sujet, deux options s'offrent à vous :

  • Vous ouvrez la connexion au début du programme et la refermez à la fin. Par exemple, la connexion pourrait avoir lieu dans entete.inc et elle sera refermée dans piedpage.inc.
  • Vous ouvrez la connexion avant chaque requête et vous la refermez tout de suite après­.

Notation objet vs notation procédurale

Dans les exemples précédents, la variable $mysqli est un objet dont la classe est mysqli. Pour accéder aux membres de cet objet, on le fera suivre d'une flèche puis du nom du membre.

Ex :

Notation objet (PHP)

$mysqli = new mysqli("localhost", "root", "", "garage");

...

$resultat = $mysqli->query(...);

echo "La requête a retourné $mysqli->affected_rows enregistrements";

...

$mysqli->close();

Il aurait également été possible d'utiliser la notation procédurale :

Ex :

Notation procédurale (PHP)

$mysqli = mysqli_connect("localhost", "root", "", "garage");

...

$resultat = mysqli_query($mysqli, ...);

echo "La requête a retourné " , mysqli_num_rows($resultat) . " enregistrements";

...

mysqli_close($mysqli);

Vérifier si la connexion a fonctionné

Il peut y avoir une foule de raisons qui empêchent une connexion, comme par exemple :

  • Serveur non disponible (problème de réseau, service arrêté, problème physique sur la machine, etc.)
  • Mauvais nom de base de données ou base de données effacée
  • Usager MySQL inexistant ou n'ayant pas les droits suffisants
  • Mauvais mot de passe
  • etc.

C'est pourquoi il importe de toujours vérifier si la connexion a fonctionné avant de poursuivre le traitement. 

Si vous omettez cette vérification, le site Web affichera, dans le haut de l'écran, un message du genre :

Warning: mysqli::mysqli(): (HY000/1049): Unknown database 'garage'

Ex :

Erreur de connexion

Voici donc la syntaxe à utiliser pour éviter cet affichage non désiré :

Syntaxe PHP

@$mysqli = new mysqli("addresse_serveur_mysql", "usager_mysql", "mot_de_passe", "base_de_donnees");

if ($mysqli->connect_errno) {

    die("Échec lors de la connexion à la base de données");

}

...   

$mysqli->close();

Ex :

PHP

@$mysqli = new mysqli("localhost", "root", "", "garage");

if ($mysqli->connect_errno) {

    die("Échec lors de la connexion à la base de données");

}

À remarquer :

  • L'ajout du @ devant l'appel de mysqli assure qu'en cas de problème, cette instruction ne générera pas l'affichage d'un message d'erreur. 
  • Si un erreur est détectée, son code sera enregistré dans $mysqli->errno. Cette variable sera donc différente de 0, ce qui est évalué comme un vrai. Donc, le code présent dans le if sera exécuté.
  • La fonction die() affiche un message à l'écran puis termine l'exécution du programme. Son utilisation est correcte dans le contexte actuel. Cependant, il faut limiter l'utilisation de die() ailleurs dans le programme car dans la plupart des cas, on pourra utiliser une technique plus élégante.
  • Dans le die(), on prend soin d'encoder les accents car le die() survient avant l'instruction qui indique au navigateur le format d'encodage utilisé.

Lire les résultats de la requête MySQL

Le résultat de la requête est un tableau à deux dimensions. Chaque ligne du tableau représente un enregistrement répondant à la condition de la requête et chaque colonne du tableau représente un champ extrait. Selon les données que vous avez entrées dans la table client, le résultat de la requête pour trouver les clients de Victoriaville pourrait être le suivant :

1 Jacynthe Courtois
2 Marc Frenette
4 Axelle Demers
8 Line Jacques

Requête qui retourne une série d'enregistrements

Afin de pouvoir utiliser le résultat de la requête, il faut extraire les lignes du tableau à l'aide de fetch_row() ou fetch_assoc(). Chaque enregistrement sera un tableau à une dimension (vecteur) et chaque champ correspondra à un élément du tableau.

Si vous utilisez  fetch_row(), vous devrez utiliser un indice pour obtenir le champ désiré. Le premier champ mentionné dans la requête sera $enreg[0], le second champ sera $enreg[1], etc.

Ex :

PHP

$requete = "SELECT client_id, client_prenom, client_nomfamille FROM client ORDER BY client_nomfamille, client_prenom";

$resultat = $mysqli->query($requete);     // exécute la requête

if ($resultat) {    // si la requête a fonctionné

   if ($mysqli->affected_rows != 0) {    // si la requête a retourné au moins un enregistrement

      echo "<ul>";

      while ($enreg = $resultat->fetch_row()) {     // extrait chaque ligne une à une

         echo "<li>$enreg[0] - $enreg[1] $enreg[2]</li>";      // affichera l'identifiant, le prénom et le nom de famille de chaque client

      }

      echo "</ul>";

   } 

   else {

      echo "Il n'y a aucun client dans le système.";

   }

}

else {

   echo "Nous sommes désolés, les données ne peuvent pas être affichées.";

}

Pour mieux décoder ce qui vient de se passer, rappelez-vous que :

  • Chaque itération de la boucle place une ligne du tableau dans le vecteur $enreg
  • Le premier champ de la requête étant client_id, nous pouvons retrouver l'identifiant du client en utilisant $enreg[0]
  • Le second champ étant le nom, on utilisera $enreg[1] pour le retrouver.
  • etc.

Requête qui retourne un seul enregistrement

Parfois, on recherchera un seul enregistrement dans une table. La logique utilisée pourra ressembler à celle-ci. Attention : puisque la requête contient une variable PHP, il serait préférable d'utiliser la technique des requêtes préparées pour protéger la base de données contre les pirates.

PHP

$requete = "SELECT client_prenom, client_nomfamille FROM client WHERE client_id='$id'";

$resultat = $mysqli->query($requete);     // exécute la requête

if ($resultat) {    // si la requête a fonctionné

   if ($mysqli->affected_rows != 0) {    // si la requête a retourné au moins un enregistrement

      $enreg = $resultat->fetch_row();  // la seule ligne du tableau sera maintenant accessible 

      echo "<p>$enreg[0] $enreg[1]</p>";      // affichera le prénom et le nom de famille du client demandé

   } 

   else {

      echo "Il est impossible de retrouver les données du client.";

   }

}

else {

   echo "Nous sommes désolés, les données ne peuvent pas être affichées.";

}

À remarquer :

  • Il est inutile d'effectuer une boucle si on ne cherchait à retrouver qu'un seul enregistrement. 
  • Le tableau des résultats, même s'il ne contient qu'une seule ligne, est un tableau à deux dimensions. Il ne peut pas être accessible directement. 
  • On extraira sa seule ligne avec $resultat->fetch_row().

fetch_assoc()

Si vous préférez travailler avec le nom des champs plutôt qu'un indice représentant leur position dans la requête, fetch_assoc() sera votre ami.

Voici un second exemple utilisant cette fois fetch_assoc().

Remarquez l'utilisation des accolades pour forcer l'interprétation de « $enreg['client'] » avant de l'inclure dans la chaîne de caractères.

Ex :

PHP

while ($enreg = $resultat->fetch_assoc()) {

   echo "<li>{$enreg['client_id']} - {$enreg['client_prenom']} {$enreg['client_nomfamille']}</li>";  

}

Pour plus d'information

« Connexions ». PHP. http://www.php.net/manual/fr/mysqli.quickstart.connections.php

« Extension mysqli ». PHP. http://php.net/manual/fr/book.mysqli.php

« Choisir une API ». PHP. http://www.php.net/manual/fr/mysqlinfo.api.choosing.php

« Interface procédurale et orientée objet ». PHP. http://php.net/manual/fr/mysqli.quickstart.dual-interface.php

Merci de partager ! Share on FacebookTweet about this on TwitterShare on Google+Share on LinkedInPin on PinterestShare on StumbleUponEmail this to someone
Catégories

9 commentaires

  1. DU

    bonjour,
    je ne trouve pas le moyen de faire fonctionner mysqli avec INSERT INTO , pour rentrer des données dans une base MySql.!

    SOS !

    merci

    • Christiane Lagacé

      L’objet $mysqli fonctionne autant pour les INSERT que pour les SELECT. La technique est exactement la même pour lancer la requête. Ce n’est que lors du traitement des données reçues que ça varie : avec un SELECT, on va souvent faire une boucle pour afficher les données alors qu’avec un INSERT, on va simplement vérifier si l’insertion a bien eu lieu.

      Puisque les données à insérer seront presque toujours issues d’informations entrées par un internaute, ce qui peut ouvrir la porte à des injections SQL ou à des attaques XSS, je vous invite à consulter l’article sur les requêtes préparées : http://christianelagace.com/php/les-requetes-preparees-pour-prevenir-certaines-injections-sql/

  2. bj

    bonjour

    Deux choses :
    L’arobase pour masquer les erreurs… Une exception serait plus justement utile et plus propre. Quant aux accolades de la boucle, elles sont strictement inutiles dans le cas présent. la ligne affected_rows est également inutile puisque c’est une requête select et même si mysqli retourne le nombre de lignes par ce biais, ce n’est pas logique étant donné que query retourne false ou le jeu.

    Cordialement.

    • Christiane Lagacé

      Bonjour,

      Je vous remercie pour vos commentaires. Je dois cependant admettre que je ne suis pas du même avis que vous.

      Tout d’abord, l’utilisation de l’arobas est absolument nécessaire lors de la connexion. Sans elle, si la connexion ne peut pas avoir lieu, PHP affichera un message d’erreur à l’écran, ce qui est tout à fait inacceptable. De plus, new mysqli() ne lève pas d’exception en cas de problème. L’utilisation de try catch demeure sans effet.

      Ensuite, je vous l’accorde, les accolades de la boucle sont effectivement facultatives puisque la boucle ne contient qu’une seule instruction. Cependant, de nombreux programmeurs préfèrent les utiliser en tout temps. Ça devient un réflexe et le code est toujours facile à lire et à maintenir.

      Concernant l’instruction qui utilise $mysqli->affected_rows pour vérifier le nombre de lignes retournées, si vous regardez la logique du premier exemple, cela permet de ne générer les balises <ul></ul> que s’il y a au moins un <li> à l’intérieur. Et dans les deux exemples, la vérification avec affected_rows permet d’afficher un message clair lorsqu’aucun enregistrement ne répond à la requête. Le programme est donc beaucoup plus convivial.

      Je suis tout de même intéressée à voir le code que vous utilisez pour vous connecter à la base de données et pour effectuer des requêtes. Pouvez-vous m’envoyer un extrait ? Qui sait, vos techniques pourraient bonifier les miennes et vice-versa !

      Christiane

      • bj

        Bonjour

        Je sais que les débutants prennent pour argent comptant les codes trouvés ici ou là, c’est pour cette raison je me suis permis ce commentaire à leur attention éventuelle. Ici, c’est l’arobase qui m’a fait réagir. Vraiment, on ne devrait plus la trouver aujourd’hui.

        <message}’ dans le fichier {$this->file} ligne({$this->line})\r\n Trace : {$this->getTraceAsString()} »;
        ….
        }

        ….
        }

        La classe qui l’utilise

        class Format_Mysqli extends CustomMysqli implements int\IException {}

        L’objet Mysqli est un singleton traditionnel. Les requêtes formatées séparément et sont envoyées par cette classe, c’est d’ailleurs en voulant la modifier pour ajouter les requêtes préparées et ajouter une constante debug que je suis arrivé sur votre site.

        class RequeteMysqli extends ObjetMysqli implements int\Irequete {
        private $_OptionQuery =  »;
        private $_ObjetBase = null;
        private $_Protection = true;
        private $_Req = null;

        function __construct($serveur, $utilisateur, $passe, $base, $encodage = ‘utf8’) {
        parent::setServeur($serveur);
        parent::setUtilisateur($utilisateur);
        parent::setPasse($passe);
        parent::setBase($base);
        try {
        $this->_ObjetBase = parent::instance();
        $this->_ObjetBase->set_charset($encodage);
        } catch (ex\Format_Mysqli $e) {
        echo $e->__toString();
        }
        }

        public function requeteSQL($chSql) {
        if ($chSql !==  »)
        throw new ex\Generique(er\Erreurs::CHAINE_VIDE.chr(10));

        if ($this->_Protection) {
        $this->_Req = $this->_ObjetBase->query($this->_ObjetBase->real_escape_string(trim($chSql)), MYSQLI_STORE_RESULT);
        } else {
        $this->_Req = $this->_ObjetBase->query(trim($chSql), MYSQLI_STORE_RESULT);
        }

        if (!$this->_Req) {
        throw new ex\Format_Mysqli(er\Erreurs::REQUETE_INVALIDE.chr(10).’Requete : ‘.$this->_ObjetBase->error. »);
        } else {
        return $this->_Req;
        }
        }

        public function Affecte() {
        return $this->_ObjetBase->affected_rows;
        }

        public function libere() {
        $this->_ObjetBase->close();
        }

        ….
        }

        Et dans la classe résultat qui attend une instance de la classe ci-dessus on trouve ceci :

        public function nbResultats($type = ‘select’) {
        if ($this->_retour != null) {
        switch ($type) {
        case ‘select’ :
        $this->_nb = count($this->_retour);
        break;
        case ‘insert’ :
        case ‘update’ :
        case ‘delete’ :
        case ‘replace’ :
        $this->_nb = $this->_ObjReq->Affecte();
        break;
        default:
        $this->_nb = 0;
        break;
        }
        }
        return $this->_nb;
        }

        La gestion des exceptions est donc possible avec mysqli et c’est heureux. Il est évidemment possible d’utiliser directement mysqli_sql_exception.

        Cordialement.

  3. bj

    Il manque une partie qui a été supprimé

    Pour le code pourquoi pas, je ne prétend nullement qu’il est meilleur que le votre. Il est orienté différement. En voilà un bout, un petit mais c’est le principe.

    La classe de gestion des erreurs mysqli

    abstract class CustomMysqli extends \mysqli_sql_exception implements IException {
    protected $message = ‘Exception inconnue. Unknown exception’;
    private $chaine;
    protected $code = 0;
    protected $fichier;
    protected $ligne;
    private $trace;
    private $Langue = ‘fr’;

    public function __construct($message = null, $code = 0) {
    if (!$message)
    throw new $this(‘Inconnue/Unknown ‘. get_class($this));

    parent::__construct($message, $code);

    }

    public function __toString() {
    if ($Langue === ‘fr’)
    return ‘Classe : ‘.get_class($this). » Message : ‘{$this->message}’ dans le fichier {$this->file} ligne({$this->line})\r\n Trace : {$this->getTraceAsString()} »;

    }
    }

    • Christiane Lagacé

      Merci pour ces extraits. C’est généreux de votre part !

      Votre approche est intéressante. Je vais l’étudier attentivement.

      Christiane