Dans ce chapitre, nous allons voir comment communiquer avec une base de données SQL depuis PHP grâce à PDO. L'objectif est de comprendre comment se connecter à une base, exécuter des requêtes, récupérer des résultats et éviter les erreurs de sécurité les plus courantes.
Avant de continuer, il est important d'avoir déjà quelques bases en SQL, vous pouvez pour cela consulter ma formation complète sur SQL. Dans la vidéo, on utilise SQLite pour simplifier la mise en place, mais le fonctionnement présenté avec PDO reste le même avec MySQL, PostgreSQL ou une autre base de données SQL.
Pourquoi utiliser PDO ?
PHP propose plusieurs façons de communiquer avec une base de données. On peut utiliser une extension dédiée à un moteur précis, comme mysqli pour MySQL ou les fonctions propres à SQLite. Le problème, c'est qu'il faut alors apprendre une API différente pour chaque type de base.
PDO apporte une couche d'abstraction. On utilise les mêmes classes et les mêmes méthodes, quelle que soit la base SQL utilisée. Dans notre cas, PDO va nous permettre de :
- nous connecter à la base de données.
- exécuter des requêtes SQL.
- récupérer les résultats.
- gérer les erreurs.
- préparer des requêtes pour éviter les injections SQL.
Se connecter avec PDO
La première étape consiste à instancier PDO. Son constructeur attend en premier paramètre un DSN, pour Data Source Name. C'est une chaîne qui indique à PDO où se trouve la base de données.
Avec SQLite, le DSN est très simple :
Avec MySQL, le DSN prendrait une autre forme, avec le nom de la base et l'hôte. Mais le principe reste le même : on crée une instance de PDO, puis on l'utilise pour lancer nos requêtes.
Exécuter une requête avec query()
Pour récupérer les articles, on peut utiliser la méthode query() :
La méthode query() exécute une requête SQL et retourne un PDOStatement. Cet objet représente la requête et les résultats associés.
On peut ensuite utiliser différentes méthodes sur ce PDOStatement :
fetch()pour récupérer une seule ligne.fetchAll()pour récupérer toutes les lignes.fetchColumn()pour récupérer une colonne particulière.execute()dans le cas d'une requête préparée.
Par défaut, fetchAll() peut retourner les résultats à la fois sous forme de tableau associatif et de tableau numérique. Cela fonctionne, mais ce n'est pas toujours le format le plus pratique à utiliser.
Choisir le mode de récupération
PDO permet de choisir la façon dont les résultats sont retournés grâce aux modes de récupération, les fetch modes.
Par exemple, on peut demander un tableau associatif :
On peut aussi récupérer les résultats sous forme d'objets anonymes :
Dans ce cas, chaque ligne devient un objet stdClass, et chaque colonne devient une propriété de l'objet.
Si on veut appliquer ce comportement partout, on peut définir un mode par défaut avec les options de PDO.
Gérer les erreurs avec les exceptions
Par défaut, certaines erreurs peuvent simplement renvoyer false. Cela oblige à vérifier le résultat de chaque requête, ce qui devient vite pénible.
PDO permet de changer ce comportement en activant les exceptions :
À partir de là, si une requête échoue, PDO déclenche une PDOException. On peut alors utiliser un try catch pour capturer l'erreur.
Pendant le développement, afficher le message d'erreur peut aider à comprendre le problème. En revanche, il ne faut pas forcément afficher une erreur SQL directement à l'utilisateur final.
Afficher les articles
Une fois les articles récupérés, on peut les parcourir avec une boucle foreach.
Même si les données viennent de la base, il faut rester prudent. Si l'utilisateur a pu saisir une valeur, on l'échappe avant de l'afficher avec htmlentities().
Éviter les injections SQL
Lorsque l'on veut récupérer un article précis, on peut être tenté d'écrire une requête en injectant directement l'ID reçu dans l'URL.
C'est une erreur de sécurité. Un utilisateur mal intentionné pourrait modifier le paramètre pour transformer la requête SQL et exécuter autre chose. C'est ce que l'on appelle une injection SQL.
PDO propose une méthode quote() pour échapper une chaîne, mais la solution la plus pratique est d'utiliser les requêtes préparées.
Utiliser les requêtes préparées
Avec une requête préparée, on indique à PDO les endroits où des paramètres seront injectés, puis on exécute la requête en fournissant les valeurs séparément.
L'avantage est que le paramètre est automatiquement traité par PDO, ce qui limite les injections SQL basiques. On utilise la même logique pour mettre à jour un article.
Si la requête échoue et que le mode exception est activé, l'erreur sera capturée par le try catch.
Insérer un nouvel article
Pour créer un nouvel article, on utilise aussi une requête préparée. La requête devient cette fois un INSERT.
Après l'insertion, PDO permet de récupérer l'identifiant du dernier enregistrement créé avec lastInsertId().
Cela permet, par exemple, de rediriger l'utilisateur vers la page d'édition de l'article qu'il vient de créer.
Récupérer les données dans une classe
PDO peut aussi remplir directement des instances de classe avec PDO::FETCH_CLASS. Dans l'exemple, on crée une classe Post qui représente un article.
On peut ensuite demander à PDO de créer des objets Post à partir des résultats.
L'intérêt est de pouvoir regrouper la logique liée à un article directement dans une classe. Par exemple, getExcerpt() permet de générer un extrait du contenu.
La vidéo montre aussi que le constructeur peut être utilisé pour transformer certaines données. Par exemple, si created_at contient un timestamp, on peut le convertir en DateTime pour ensuite l'afficher avec format().
Utiliser les transactions
Les transactions permettent d'exécuter plusieurs opérations comme un seul bloc. Si une opération échoue, on peut annuler tout ce qui a été fait avant.
On démarre une transaction avec beginTransaction(), puis on valide avec commit().
Si quelque chose se passe mal, on peut faire un rollback().
Toutes les opérations effectuées depuis le début de la transaction sont alors annulées. C'est utile quand plusieurs suppressions ou insertions sont liées entre elles. En revanche, il ne faut pas utiliser une transaction pour un simple INSERT, car cela a un impact sur les performances.