PDO : Comment ça marche - Version imprimable +- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org) +-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38) +--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51) +--- Sujet : PDO : Comment ça marche (/showthread.php?tid=4553) Pages :
1
2
|
PDO : Comment ça marche - Ekilio - 22-01-2010 Bonjour à tous, Ayant commencé depuis quelques mois ma transition de mysql_* à PDO, je viens vous proposer un petit retour, sous forme de tutoriel, à ce sujet. Le but sera de bien comprendre PDO, ses avantages et son mode d'emploi, puis de proposer quelques codes pour l'utiliser. Note : je n'aborderais pas ici les outils utilisant PDO comme Zend_Db ou Symfony, cela déborderait du cadre de cet article. I/ PDO, pourquoi ? Tout d'abord, la question qui vient à l'esprit lors du passage vers PDO, c'est : pourquoi faire ? Après tout, ça marchait très bien avant ! Et bien, il y a plusieurs raisons à cette transition. La première raison est purement pratique : lors de l'arrivée de PHP 6, d'ici quelques temps tout de même, les fonctions mysql_* et mysqli_* seront dépreciées et passées en module de PEAR ; par conséquent, petit à petit, les hébergeurs cesseront le support de ces fonctions. Bien sûr, il y a du temps, mais autant prendre directement les bonnes habitudes, non ? La seconde raison est liée à la sécurité : l'utilisation de PDO et des requêtes préparées permet, nous le verront, de supprimer les risques d'injections SQL dans vos scripts. La troisième raison est l'interopérabilité : en utilisant PDO, vous pouvez facilement changer de SGBD, alors qu'avec les fonctions mysql_*, c'est tout votre code que vous deviez reprendre. Il y a d'autres raisons, mais ce sont les plus importantes à mes yeux. II/ PDO, les bases PDO est séparée en trois classes différentes, et toutes aussi utiles les unes que les autres : PDO, PDOStatement, PDOException. PDO est la classe de base : c'est elle qui gère les requêtes et les relations à la base au sens propre. PDOStatement est la classe qui gère à la fois la préparation des requêtes et leurs retours ; c'est la classe de résultats, en quelques sortes. PDOException est, comme son nom l'indique, la classe d'exception de PDO. Je ne m'étendrais pas là-dessus, le système d'Exception est complexe. III/ Les DSN et la connexion Commençons par le commencement : la connexion à une base de données. Pour se connecter, PDO utilise ce que l'on appelle un DSN (Data Source Name). Pour faire simple, c'est une chaine de caractères qui contient la description de la base sur laquelle on souhaite se connecter. Un DSN ressemble à ceci : Code : mysql:dbname=mabase;host=127.0.0.1 Examinons les parties importantes. Le "mysql" en début de chaine indique que l'on se connecte à un serveur mysql. Actuellement, PDO supporte MySQL, MS SQL Serveur, Firebird, Interbase, IBM, Informix, Oracle, ODBC, DB2, PostgreSQL, SQLite et 4D (attention, certains de ces supports sont expérimentaux). Le dbname= indique le nom de la base de données où nous allons nous connecter (ici "mabase"). C'est l'équivalent de l'ancienne fonction "mysql_select_db" ou de la commande SQL USE Le host= indique le nom du serveur sur lequel nous allons nous connecter ; ici 127.0.0.1, donc en local. Ce DSN doit être passé à PDO, ainsi que deux autres arguments : le nom d'utilisateur et le mot de passe. Tout ceci dans le constructeur ; la création d'une connexion avec PDO ressemble donc à ça : Code PHP :
Simple, non ? Il est maintenant important de gérer les exceptions, cela se fait comme ceci : Code PHP :
Et voila, tout est prêt à être utilisé ! IV/ Les requêtes non-préparées Le premier usage, le plus simple et le plus rapide (mais le moins sécurisé) de PDO, est l'utilisation des requêtes non-préparées. Pour faire simple, les requêtes non-préparées sont l'équivalent de notre ancien mysql_query : on prends du texte et on l'execute directement sur notre serveur. C'est la méthode la plus rapide si on ne doit executer une requête qu'une seule fois et qu'elle n'a pas de paramètres. L'execution de requêtes non-préparées se fait via la fonction query, comme ceci : Code PHP :
L'object obtenu en réponse sera du type PDOStatement, nous verrons ensuite comment traiter son résultat. Notez que les chaines passées via query doivent impérativement être sécurisées ; si vous tenez absolument à utiliser des paramètres entrés par l'utilisateur dans de telles chaines, pensez à utiliser une fonction d'echapement. Notez que mysql_real_escape_string ne fonctionne pas avec PDO ; vous pouvez utiliser $pdo->quote($texte) pour la remplacer, mais d'une manière générale, les requêtes avec des paramètres doivent être préparées. V/ Utiliser l'object PDOStatment pour un résultat Nous l'avons vu, la fonction query() retourne un objet de type PDOStatment. Cet objet peut être utilisé de plusieurs façons. 1) La fonction fetch() La première méthode, et la plus proche des anciennes fonctions mysql_*, est l'utilisation de sa méthode fetch(). Fetch() fonctionne globalement comme les anciens mysql_fetch_* : cela renvoi la ligne suivante sous la forme que l'on défini par une constante. Voici un exemple : Code PHP :
Les valeurs possibles sont les suivantes pour le paramètres de fetch (le type d'association) : PDO::FETCH_ASSOC pour obtenir un table associatif (colonne => valeur) PDO::FETCH_BOTH pour un tableau association et numérique (colonne => valeur ET numero_de_colonne => valeur). C'est la valeur par défaut. PDO::FETCH_CLASS pour une classe que vous définissez. J'y reviens un peu plus tard. PDO::FETCH_INTO pour une classe définie et déjà existante que vous mettez à jour à chaque fetch (j'y reviens aussi) PDO::FETCH_LAZY pour créer un objet standard (stdClass) dont les variables sont créées uniquement lorsqu'on tente d'y faire appel. PDO::FETCH_NUM pour un tableau numérique PDO::FETCH_OBJ pour un objet standard (stdClass) dont les propriétés sont les colonnes de la requête Maintenant, revenons un instant sur les type de CLASS. Le principe est le suivant : vous fournissez à PDO un objet de votre choix, que vous avez créé ; et cet objet sera rempli automatiquement par PDO avec les valeurs sélectionnées. Dans le cas de PDO::FETCH_CLASS, vous devez utiliser au préalable la fonction setFetchMode() sur votre objet de résultat. Cette fonction permet de définir le mode de distribution pour tous les fetch suivants, et si son mode est sur FETCH_CLASS, elle prends en second paramètre le nom de la classe, comme ceci : Code PHP :
A partir de là, les propriétés de votre classe "user" seront remplies automatiquement à chaque fetch (la méthode magique __set() fonctionne). Voici un exemple : Code PHP :
Il existe une autre variante du type CLASS, la constante PDO::FETCH_CLASSTYPE. Cette constante va fonctionner globalement comme la précédente, à ceci près que le nom de la classe à créer ne doit pas être spécifié par setFetchMode : il sera directement récupéré dans la première colonne du résultat. Enfin, la troisième constante est la constante PDO::FETCH_INTO ; elle fonctionne globalement comme FETCH_CLASS sauf qu'elle met à jour le même objet à chaque fetch() au lieu d'en créer un nouveau à chaque fois. 2) La fonction fetchAll() Une seconde façon d'utiliser le résultat est de faire appel à la fonction fetchAll(). Cette fonction interviendra exactement de la même manière que fetch(), et prends les mêmes arguments, mais renvoi la totalité des résultats sous forme d'un tableau de résultats du type que vous aurez défini. Voici un exemple : Code PHP :
3) Par foreach() Enfin, la troisième méthode pour parcourir les résultats est d'utiliser la fonction foreach, directement sur notre objet de résultats, comme ceci : Code PHP :
Il existe une quatrième méthode pour récupérer les résultats, mais nous nous y intéresserons plus tard. VI/ Le commit et les transactions Théorie générale D'abord, un peu de théorie. Une transaction, c'est un système qui permet de demander à MySQL de considérer un ensemble de requêtes comme un "bloc", qui doit s'executer en entier ou pas du tout. Prenons un exemple pratique : imaginons un jeu dans lequel nos joueurs ont un système d'équipement. Ils peuvent donc vouloir équiper un objet depuis leurs inventaire. Si il le font, cela correspondrait aux requêtes suivantes : - Suppression de l'objet de l'inventaire - Ajout de l'objet dans l'équipement - Mise à jour des statistiques du joueur Imaginons maintenant que la seconde requête plante. Dans ce cas, soit le joueur voit l'objet disparaitre mais ses stats être tout de même améliorées, soit (pire) il le voit disparaitre sans amélioration. Dans les deux cas, il perd son objet, ce qui n'était pas prévu. Le principe des transactions est justement là pour remédier à ça ! Comment ça marche ? C'est simple ! On utilise deux termes, le "commit" et le "rollback". Le commit veux dire "effectuer toutes les modifications des requêtes précédentes" ; et le rollback veux dire "annuler toutes les modifications des requêtes précédentes". En d'autre termes, cela donnerais cela : Code : DEBUT DE TRANSACTION Et là, si l'un des requêtes échoue, les changements seront totalement annulés, comme si rien ne s'était produit ; et sinon, ils s'effectueront. Notez au passage qu'il était possible en mysql_* d'utiliser les transactions ; simplement, il fallait les gérer à la main. Pratique dans PDO PDO propose un système bien plus pratique pour gérer les transactions : les trois fonctions beginTransaction(), commit() et rollBack(). Lorsqu'on ne les utilise pas, certains SGBD (dont MySQL) ont un mode "autocommit", c'est à dire qu'ils font un commit après chaque requête ; néanmoins, ce mode plante un peu (en tous cas chez moi) donc je vous suggère fortement d'utiliser ces fonctions. Reprenons le pseudo-code précédent et utilisons-le en version PDO : Code PHP :
Notez qu'avant le commit(), aucune modification n'est réellement apportée à la base de données ; si nous avions placé un die() avant celui-ci, la base n'aurait pas été modifiée du tout. VII/ Les requêtes préparées Allez, abordons maintenant le gros morceau : les requêtes préparées ! Ce sont elles qui font la véritable force de PDO et son utilité principale. Un peu de théorie Pour commencer, revoyons rapidement le principe des requêtes préparées. Vous connaissez, j'imagine, le principe du MVC et celui selon lequel il faut séparer le code de l'affichage ; et bien, ici, c'est un peu la même chose, on veux séparer le code des données. N'oublions pas que les SGBD sont fait essentiellement pour stoquer et restituer des données ; et que SQL n'est qu'une façon de leur indiquer la manière dont on souhaite que ces données soient stoquées ou restituées. Il est donc logique d'imaginer une séparation stricte : d'un coté, le code SQL pour dire ce que l'on souhaite faire, et de l'autre, les données que l'on souhaite voir traiter avec ce code. Les requêtes préparées ont deux avantages principaux. Tout d'abord, parce qu'on sépare strictement code et données, il est impossible de faire passer des données pour du code (ce qui était le principe des injections SQL) ; et de ce fait, il n'est pas nécessaire de s'inquiéter de la sécurité des requêtes. Ensuite, cette séparation permet une mise en cache par le moteur du code : il a été interprété une fois, et même si on souhaite l'executer pour trente données différentes, il ne sera toujours interprété qu'une seule fois. Gain de temps, de performances, bref, l'extase. Note : Mysql dans ses version antérieurs à la 4.1 ne supporte pas nativement les requêtes préparées ; PDO émule dans ce cas ces requêtes, et vous pouvez donc les utiliser tout de même, bien que le gain de temps sera moindre. Application en SQL Rapidement, à quoi ressemble une requête préparée en SQL ? Il y a deux méthodes. La plus simple consiste à utiliser des ? à la place des données, comme ceci : Code : INSERT INTO user (id, nom) VALUES (?, ?) Cette syntaxe à l'avantage de la rapidité d'écrite... et l'inconvénient d'être totalement impossible à maintenir sur le long terme, car on se réfère alors aux ? dans l'ordre, et donc en cas de changement, la requête tombe à l'eau. La seconde méthode consiste à nommer des variables avec la syntaxe :variable pour indiquer laquel nous interesse, comme ceci : Code : INSERT INTO user(id, nom) VALUES (:id, :nom) Dans ce cas, il ne nous restera plus qu'à indiquer à notre moteur à quoi correspond :id et :nom, et l'execution se fera. Cette syntaxe est plus lisible et surtout beaucoup plus simple à maintenir ; c'est celle que j'utiliserais dans ce tutoriel. Note : Certains SGBD supporte l'une des deux syntaxes, et pas l'autre. Il n'est pas nécessaire de vous en soucier : PDO utilisera automatiquement le type le plus approprié, que vous utilisiez l'un ou l'autre. Application en PDO La création et l'execution de requêtes préparées en PDO se fait en deux étapes : tout d'abord, on prépare la requêtes ; puis on l'execute en passant les données à utiliser. La première partie, la création, se fait via la fonction prepare() et retourne un objet de type PDOStatement, que nous avons déjà vu précédement pour les résultats. La seconde partie se fait via la méthode execute() de notre objet PDOStatement, qui prends en paramètre un tableau associatif clef => valeur pour les différents noms dans la requête. Voici un exemple : Code PHP :
Et voila ! Ce n'était pas difficile, n'est-ce pas ? VIII/ Les binds Il existe une autre méthode pour attribuer des valeurs ou les récupérer dans le résultat : les binds. Le principe est de ne plus passer par un intermédiaire (tableau ou autre) mais directement d'associer une variable ou une valeur à une partie de la requête. Binder un paramètre à une valeur La première utilité de ceci est la plus simple : on remplace l'utilisation du tableau associatif de la fonction execute() par une utilisation directe. Autrement dit, au lieu de faire : Code PHP :
On peut utiliser la méthode bindValue() de l'objet PDOStatment pour associer une valeur, comme ceci : Code PHP :
Cette méthode vous permet de ne pas avoir à définir tous vos paramètres en même temps. Binder un paramètre à une variable La seconde méthode est encore plus pratique pour certaines applications. Le principe va être de passer en paramètre une variable (qui sera récupérée par référence, toujours) et de l'associer complètement à un paramètre de la requête. Ainsi, si la variable change, la valeur du paramètre changera également, sans avoir à executer d'autres instruction. Cela se fait via la méthode bindParam() de l'objet PDOStatement. Voici l'exemple : Code PHP :
Binder un champ de résultat à une variable La troisième utilisation du bind est aussi une manière supplémentaire de récupérer des résultats : vous pouvez directement associer un champs à une variable. C'est le même principe que pour binder une variable à un paramètre, mais à l'envers : cette fois, chaque fetch du résultat se traduira par une modification de la variable. Cela se fait par le biais de la méthode bindColumn() de l'objet PDOStatement, et voici l'exemple : Code PHP :
La suite au prochain épisode ! RE: PDO : Comment ça marche - Ter Rowan - 22-01-2010 dommage que tu ne l'ais pas fait y a trois mois ^^ quand je cherchais à passer à pdo :p concernant les transactions, ta solution des if pour le roll back me gène un peu. En effet si on a une succession de requêtes, le code aura un nombre de if imbriqué trop important (donc moins lisible/maintenable) Ne pourrait on pas plutôt utiliser les exceptions dans ce cas du genre try { requete 1 requete 2 requete 3 .... requete n commit } catch(e) { rollback traitement de (e) } ainsi, je pense (mais bon pas vraiment habitué à try catch) que si requete 3 plante et génère une erreur, on aura le roll back pour les requetes 1 et 2 et ce sans n if de partout imbriqué RE: PDO : Comment ça marche - Ekilio - 22-01-2010 C'est tout à fait vrai Mais mon tuto se veut tourner vers PDO et ses fonctionnalités plus que vers le code en général. Il y a plusieurs exemples qui ne sont pas pratiques ou pas adaptés à la réalité, mais qui servent pour montrer une fonctionnalité Le tuto n'est pas tout à fait fini, la suite sera cet aprem je pense ! RE: PDO : Comment ça marche - Zamentur - 22-01-2010 Concernant les requêtes préparées, est ce qu'il y a une erreur si on passe un argument de type chaine de caractère à une clef qui devrait recevoir un entier? Par exemple si on passe une chaine vide est ce que ce sera convertit en 0? Je pose la question car je n'utilise pas PDO, j'ai une classe d'abstraction sql qui gère ces problématiques et même d'autres, cependant j'envisage d'écrire un pilote PDO pour la compatibilité php6. En tout cas très bon tuto. RE: PDO : Comment ça marche - custmax - 22-01-2010 Super ! Ca me permet de découvrir PDO, depuis le temps que je me dis d'apprendre. Merci pour ce tuto ! (en attendant la suite ^^) RE: PDO : Comment ça marche - Mysterarts - 23-01-2010 Merci à toi pour ce tuto, je me préparais justement à m'y mettre ! C'est chose faite grâce à ce topic qui m'a permis d'y mettre un pied : reste plus qu'à appliquer. (Bah oui, je traine, je sais...) Mysterarts RE: PDO : Comment ça marche - Sephi-Chan - 23-01-2010 Merci Ekilio pour ces ressources ! Je me réjouis de voir autant de discussions à propos de PDO ces derniers temps. Sephi-Chan RE: PDO : Comment ça marche - Ekilio - 25-01-2010 Pour répondre à Zamentur : de ce que j'ai vu, il y a une conversion faite si le type attendu n'est pas le bon (par exemple, la table attends un entier et c'est un string qui est entré). Au passage, il existe également un troisième paramètre aux deux options de bind (bindValue et bindParam) qui est le $data_type, et qui permet de forcer un type particulier de données. Il prend comme valeur une des constantes suivantes : PDO:ARAM_BOOL PDO:ARAM_NULL PDO:ARAM_INT PDO:ARAM_STR PDO:ARAM_LOB (Représente un objet de type "large" en SQL, comme le contenu d'un champs de type blob par exemple) PDO:ARAM_STMT (Représente un jeu de résultat d'une autre requête préparée, n'est pas supporté par tous les moteurs - aucune idée si MySQL le supporte ou pas) PDO:ARAM_INPUT_OUTPUT (Je me contente de copier la doc vu que je ne comprends pas parfaitement ce paramètre : Spécifie que le paramètre est un paramètre INOUT pour une procédure stockée. Vous devez utiliser l'opérateur OR avec un type de données explicite PDO:ARAM_*. ) Sinon, la fin du tuto (il manque encore une partie) ce soir ou demain je pense RE: PDO : Comment ça marche - Joojo - 02-03-2010 Dans plusieurs scripts et tuto sur la PDO, ils utilisent la fonction closeCursor() après chaque requête, utile ou pas? A ce que j'ai lu cela évite des erreurs et "libère la connexion du serveur, permettant ainsi à d'autres requêtes SQL d'être exécutées, mais quitte la requête, permettant ainsi qu'elle soit de nouveau exécutée". http://www.manuelphp.com/php/function.pdostatement-closecursor.php RE: PDO : Comment ça marche - Freygolow - 07-06-2010 Salut, J'ai effectué quelques tests de performances qui ne sont pas cohérents avec ce qui a été dit. Il est dit (ici et partout sur le net) que le paramètre de fetch par défaut est PDO::FETCH_BOTH or les résultats que je trouvent diffèrent: Tests effectués sur plus de 20000 enregistrements: fetch(): page génerée en 60ms fetch(PDO::FETCH_BOTH): page générée en 72ms fetch(PDO::FETCH_ASSOC): page génerée en 64ms En revanche avec fetchAll, le résultat est cohérent. Je trouve que la différence est quand même élevée. Quelqu'un aurait une réponse à ce mystère? :p PS: Même l'espace mémoire utilisée diffère entre un fetch(PDO::FETCH_BOTH) et un fetch() |