N'utilisez plus les fonctions mysql_*

Dans cet article, nous verrons en quoi il est préférable d'utiliser PDO et d'oublier les fonctions telles que mysql_*

0. Sommaire

  1. Introduction
  2. Mise en place de l'environnement
  3. Instanciation de l'objet PDO
  4. Requêtes simples
  5. Requêtes préparées
    1. Sans bind de valeurs
    2. Avec bind de valeurs
  6. Les transactions
  7. Conclusion
  8. Ressources
  9. Notes

1. Introduction

La sortie de PHP 5 le 13 Juillet 2004, a apporté son lot de nouveauté. Parmis eux, se trouve la librairie PDO, originalement fournie en tant qu'extension PECL, mais ensuite adaptée dans le coeur de PHP dans sa version 5.1. PDO fournit une abstraction des bases de données, permettant ainsi au développeur de communiquer avec une base de donnée sans (trop) se soucier du serveur avec qui il communique (MySQL, PostgreSQL, MsSql, Oracle, etc). La puissance de PDO réside dans le fait que cette librairie est écrite en C, contrairement aux Pear:DB, AdoDb et companie, écrite elles, en Php. De ce fait, on constate un gain de performance et le développeur n'a pas à se soucier de télécharger les classes * au cas ou * le serveur ne les aurait pas à disposition (cas de PEAR par exemple).

Mais alors pourquoi trop de monde utilise encore mysql_* ?

Nous verrons dans cet article que PDO est très simple d'usage et qu'il existe maintenant plus aucune excuse à utiliser mysql_* !

2. Mise en place de l'environnement

Afin d'être clair dans cet article, je vais vous indiquer mes conventions de nommage et quelle base de donnée je vais utiliser. Mes variables sont écrites de cette manière $xVar, ou x représente le type (b pour Boolean, s pour String, etc) et Var un nom explicite donné.

Nous utiliserons la base de donnée mysql sur un serveur MySQL 5.0.51.

3. Instanciation de l'objet PDO

Il est possible d'établir une connexion à une base de donnée de trois manière différentes.

  • La première consiste à indiquer toutes les infos dans le constructeur de PDO. C'est la manière générale utilisée.
  • L'autre manière est d'indiquer un fichier "credential" contenant toutes les informations nécéssaires mais stockées dans le serveur dans un répertoire particulier. Cela permet par exemple au développeur de se connecter à une base de donnée tout en ayant aucune informations sur le mot de passe de celle-ci par exemple.
  • La dernière méthode et d'indiquer ses informations dans le fichier de configuration de php, et d'instancier PDO avec juste le nom de la variable donnée dans php.ini.

Dans notre cas, nous utiliserons la méthode standard.

Voici donc l'instanciation de notre Objet PDO :

<?php
try {
     $oPdo = new PDO ('mysql:dbname=mysql;host=127.0.0.1', 'user', 'passwd');
}
catch (PDOException $oPE) {
     echo 'Impossible de se connecter à la base de donnée, raison : '.$oPE->getMessage ();
     exit ();
}
?>

Et voila ! Notre objet est prêt !

4. Requêtes Simples

Pour effectuer des requêtes rapidement, PDO propose deux méthodes, PDO::query et PDO::exec. PDO::query retourne un jeu de résultat avec l'objet PDOStatement tandis que PDO::exec retourne le nombre de lignes affectées par la requête. Comme vous l'aurez compris, l'utilisation d'une ou l'autre des méthodes dépendra de votre requête ! Si vous désirez par exemple parcourir la liste de vos utilisateurs sur la base MySQL, vous n'avez qu'à faire :

<?php
// On utilise l'objet PDO précédement crée
$oUserList = $oPdo->query ('SELECT User FROM user ORDER BY User;');
// Vous pouvez définir le type de récupération des valeurs de PDO.
// Comme pour les fonctions mysql_fetch_*,
// il existe quelque chose de similaire avec les constantes PDO :
// PDO::FETCH_*
// Ainsi on veux que notre jeux de résultat nous retourne un tableau associatif
$oUserList->setFetchMode (PDO::FETCH_ASSOC);

// Maintenant on peux boucler sur les valeurs :
while ($aRow = $oUserList->fetch ()) {
     echo 'Utilisateur : '.$aRow['User'].'<br />';
}
?>

Si vous souhaitez supprimer l'utilisateur test et savoir si la requête s'est  bien déroulée, vous n'avez qu'à faire :

<?php
// On utilise l'objet PDO précédement crée
$iAffected = $oPdo->exec ('DELETE FROM user WHERE User = "test";');
echo 'Vous avez supprimés '.$iAffected.' utilisateurs.';
?>

C'est simple non ?

5. Requêtes préparées

1. Sans bind de valeurs.

PDO vous permet de préparer des requêtes à l'avance histoire de vous simplifier la vie. Ainsi, vous pouvez appeler une requête plusieurs fois par exemple !

<?php
$oListUser = $oPdo->prepare ('SELECT User FROM user ORDER BY User;');
// Vous pouvez récuperer une liste d'utilisateurs :
$oListUser->execute ();
while ($aRow = $oListUser->fetch ()) {
     echo $aRow['User'].'<br />';
} 
// ensuite vous effectuez diverses actions comme supprimer un utilisateur

// Puis vous exécutez la requête a nouveau. Vous aurez maintenant un utilisateur en moins dans la liste !

$oListUser->execute ();
while ($aRow = $oListUser->fetch ()) {
     echo $aRow['User'].'<br />';
}
?>

2. Avec Bind des valeurs

L'intérêt des requêtes préparées montre toute son importance lorsque vous voulez appeler une requête avec une valeur qui change dans la clause WHERE par exemple. Changeons de base de donnée, et orientons-nous maintenant vers un gestionnaire de livre. Imaginez que vous avez une table "category" qui contient une liste de catégorie pour les livres (Science-Fiction, Aventure, etc) et une table "books" qui contient la liste des livres. Dans cette table "books", vous avez une clée étrangère "category_id" qui pointe sur la table "category" pour spécifier à quelle catégorie appartient ce livre. Imaginez que vous voulez faire du nettoyage. Vous désirez mettre la valeur category_id à 0 pour tous les livres n'ayant plus de catégorie (la catégorie ayant été supprimée dans le passé par exemple).

Pour ce faire, vous pouvez procéder ainsi :

<?php
// On va lister tous les livres
$oBooksList = $oPdo->prepare ('SELECT book.id, book.title, category.name AS category FROM book LEFT JOIN category ON (book.category_id = category.id) ORDER BY book.title');
// On veux un tableau associatif
$oBooksList->setFetchMode (PDO::FETCH_ASSOC);
// Vous pouvez aussi directement définir le type de récupération dans la fonction fetch (),
// mais je préfère l'appeler une fois, plutôt qu'à chaque itérations !
// (de ce genre : while ($aRow = $oBooksList->fetch (PDO::FETCH_ASSOC)) {)

// On prépare maintenant la requête à effectuer pour la mise à jour :
$oUpdateBook = $oPdo->prepare ('UPDATE book SET category_id = 0 WHERE id = ? LIMIT 1;');

// On liste les livres :
while ($aRow = $oBooksList->fetch ()) {
     if (empty ($aRow['category'])) {
          // Pour tous les livres n'ayant plus de catégories
          // On met à jour la table pour mettre la catégorie à 0
          $oUpdateBook->execute (array ($aRow['id']));
     }
}
?>

Vous voyez, c'est vraiment simple ! :)

Les requêtes préparées se font de deux manières possible. L'exemple précédent montre la méthoed avec le point d'interrogation "?". Pour ce faire, vous mettez autant de point d'interrogation que de valeurs à remplacer, puis vous donnez à la méthode execute un tableau numérique, contenant les diverses valeurs dans le bon ordre.

Voici un exemple :

<?php
$oReq = $oPdo->prepare ('SELECT User FROM user WHERE Select_priv = ? AND Insert_priv = ? AND Update_priv = ?;');
$oReq->execute (array ('Y', 'N', 'Y'));
?>

L'autre méthode consiste à donner des noms dans la requête SQL préfixée par un deux points ":". Vous donnerez alors comme paramètre à la méthode PDO::execute un tableau associatif : Nom_de_valeur_dans_la_requete_sql => valeur.

Voici un exemple plus parlant :

<?php
// Remarquez que les valeurs sont préfixées par un "deux-points" qui permet à
// PDO de faire la différence entre de vraie valeurs et des valeurs à binder.
$oReq = $oPdo->prepare ('SELECT User FROM user WHERE Select_priv = :Yes AND Insert_priv = :No AND Update_priv = :Yes;');

// Vous pouvez enlever les deux points dans le tableau associatif, mais si vous les laissez,
// ca fonctionnera aussi
$oReq->execute (array ('Yes' => 'Y', 'No' => 'N'));
?>

Notez que les deux points ne sont pas obligatoires dans le tableau associatif !

Voila ! Vous êtes maintenant incollable sur la préparation de requête sous PDO ! :)

6. Les transactions

Je ne vais pas m'attarder ici sur ce qu'est une transaction, vous avez wikipedia pour cela, la documentation de MySQL ou même Google.

Concrètement, PDO est fournit avec trois méthodes, PDO::beginTransaction ();, pour démarrer une transaction, PDO::rollBack (); pour annuler les changements intervenus après l'appel à PDO::beginTransaction (); et PDO::commit (); pour valider les changements.

Voici un exemple de son implémentation : (très basique, je vous l'accorde !)

<?php
// On va tenter quelque chose de risqué !
$oPdo->beginTransaction ();
$oPdo->query ('DELETE FROM user;');
// Ohh !! non !!! il fallait pas faire ca !!!
// C'est pas grave ! :p un petit retour en arrière :
$oPdo->rollBack ();
// En fait on voulait supprimer :
$oPdo->query ('DELETE FROM user WHERE User = 'test' LIMIT 1;');

// Et on valide les modifications
$oPdo->commit ();
?>

Bien sûr, vous trouverez un intérêt plus grands à l'usage des transactions lors d'actions asynchrones.

Imaginez un client qui achète un produit. Vous appelez le webservice de votre banque pour le facturer, puis vous enregistrez son paiement dans votre base de donnée, et ensuite vous recevez une réponse de votre banque comme quoi son paiement à été refusé. A ce moment là vous annulez les modifications dans la base de donnée avec un simple rollBack, plutôt que rechercher son dernier paiement, voir s'il était à la date du jour, etc.

C'est plus simple n'est-ce pas ?

7. Conclusion

PDO est vraiment un outil simple à comprendre, facile à manipuler, puissant et rapide. Malgré cela, de nombreuses personnes persistent encore à vouloir utiliser les fonctions mysql_* pour des raisons de retro-compatibilités.

Sachez que PHP 4 n'est plus soutenu pour l'équipe de PHP. Il serait donc grand temps de passer à PHP 5, et d'utiliser PDO !

A ceux qui prétexte que de nombreux serveurs utilisent encore php4, je leur dirai deux choses :

  • Si vous proposez des scripts/applications compatible Php4, vous donnerez une bonne raison aux administrateurs systèmes de laisser leur serveur en Php4
  • De nombreuses failles ont été découvertes dans la version 4 de PHP, donc même si ce n'est pas pour des raisons de compatibilités, ayez au moin le bon sens de le faire pour des raisons de sécurités !

Votre boss refusera peut-être de mettre à jour le serveur par crainte de rendre ses sites inaccessibles a cause d'un changement de configuration, mais demandez lui s'il préfère une perte des données pour manque de sécurité :p

8. Notes

Bien entendu, je n'ai pas traité toutes les fonctionnalités de PDO, il existe trop de subtilités et je vous conseille de vous documenter par vous même. Le site de php.net propose une documentation claire et précise sur les différents cas d'utilisation de PDO, pourquoi s'en priver ?

Alors maintenant tous à vos codes, et oubliez ces mysql_* !!!
(ca marche aussi pour pgsql_* et oci_* ! :p)

Posted by Cyril Nicodème