Chapitre 4 PDO 2022
Chapitre 4 PDO 2022
Chapitre 4 PDO 2022
L'extension PHP Data Objects (PDO) définit une excellente interface pour
accéder à une base de données depuis PHP. Chaque pilote de base de données
implémenté dans l'interface PDO peut utiliser des fonctionnalités spécifiques de
chacune des bases de données en utilisant des extensions de fonctions. Notez que
vous ne pouvez exécuter aucune fonction de base de données en utilisant
l'extension PDO par elle-même ; vous devez utiliser un driver PDO spécifique à la
base de données pour accéder au serveur de base de données.
PDO fournit une interface d'abstraction à l'accès de données, ce qui signifie que
vous utilisez les mêmes fonctions pour exécuter des requêtes ou récupérer les
données quel que soit la base de données utilisée. PDO ne fournit pas une
PDO est fourni avec PHP 5.1 et est disponible en tant qu'extension PECL pour
PHP 5.0 ; PDO requiert les nouvelles fonctionnalités OO fournies par PHP 5 et
Page 1
Se connecter à la base de données en PHP
Pour pouvoir travailler avec la base de données en PHP, il faut d'abord s'y
connecter.
Nous allons apprendre dans ce chapitre à lire des données dans une BDD (base
de données). Or, je vous rappelle que PHP doit faire l'intermédiaire entre vous et
MySQL.
Problème : PHP ne peut pas dire à MySQL dès le début « Récupère-moi ces
valeurs ». En effet, MySQL demande d'abord un nom d'utilisateur et un mot de
passe.
Il va donc falloir que PHP s'authentifie : on dit qu'il établit une connexion avec
MySQL. Une fois que la connexion sera établie, vous pourrez faire toutes les
opérations que vous voudrez sur votre base de données !
Page 2
Nous allons ici utiliser PDO car c'est cette méthode d'accès aux bases de données
qui va devenir la plus utilisée dans les prochaines versions de PHP.
D'autre part, le gros avantage de PDO est que vous pouvez l'utiliser de la même
manière pour vous connecter à n'importe quel autre type de base de données
(PostgreSQL, Oracle…) (figure suivante).
Vous pourrez donc réutiliser ce que vous allez apprendre si vous choisissez
d'utiliser une autre base de données que MySQL.
Page 3
Pour l'instant, nous faisons des tests sur notre ordinateur à la maison. On
dit qu'on travaille « en local ». Par conséquent, le nom de l'hôte sera
localhost.
Quant au login et au mot de passe, par défaut le login est root et il n'y a
pas de mot de passe.
<?php
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
?>
Voici donc comment on doit faire pour se connecter à MySQL via PDO sur la base
test :
<?php
// Sous WAMP (Windows)
$bdd = new PDO(' mysql : host = localhost ; dbname = test ; charset=utf8', 'root', ' ');
?>
La ligne de code qu'on vient de voir crée ce qu'on appelle un objet $bdd.
Le premier paramètre (qui commence par mysql) s'appelle le DSN : Data Source
Name. C'est généralement le seul qui change en fonction du type de base de
données auquel on se connecte.
Page 4
3. Tester la présence d'erreurs
Si vous avez renseigné les bonnes informations (nom de l'hôte, de la base, le login
et le mot de passe), rien ne devrait s'afficher à l'écran.
Toutefois, s'il y a une erreur (vous vous êtes trompés de mot de passe ou de nom
de base de données, par exemple), PHP risque d'afficher toute la ligne qui pose
l'erreur, ce qui inclut le mot de passe !
Vous ne voudrez pas que vos visiteurs puissent voir le mot de passe si une erreur
survient lorsque votre site est en ligne. Il est préférable de traiter l'erreur.
En cas d'erreur, PDO renvoie ce qu'on appelle une exception qui permet de
« capturer » l'erreur. Voici comment je vous propose de faire :
<?php
try
{$bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
}
catch (Exception $e)
{
die('Erreur : ' . $e->getMessage());
}
?>
<?php
try {
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
foreach($dbh->query('SELECT * from FOO') as $row) {
print_r($row);
}
$dbh = null;
} catch (PDOException $e) {
print "Erreur !: " . $e->getMessage() . "<br/>";
die();
}
?>
Page 5
il faut savoir que PHP essaie d'exécuter les instructions à l'intérieur du bloc try.
S'il y a une erreur, il rentre dans le bloc catch et fait ce qu'on lui demande (ici, on
arrête l'exécution de la page en affichant un message décrivant l'erreur).
Si au contraire tout se passe bien, PHP poursuit l'exécution du code et ne lit pas
ce qu'il y a dans le bloc catch.
<?php
$dbh = new PDO('mysql:host=localhost;dbname=test', $user, $pass);
// utiliser la connexion ici
$sth = $dbh->query('SELECT * FROM foo');
// et maintenant, fermez-la !
$sth = null;
$dbh = null;
?>
Dans un premier temps, nous allons apprendre à lire des informations dans la
base de données, puis nous verrons comment ajouter et modifier des données.
Page 6
Voici les cinq premières entrées qu'elle contient (il y en a une cinquantaine en tout !) :
Notre objectif est de créer une page PHP qui va afficher ce que contient la
table jeux_video.
On récupère ce que la base de données nous a renvoyé dans un autre objet que
l'on a appelé ici $reponse.
<?php
$reponse = $bdd->query('SELECT * FROM jeux_video');
?>
Vous imaginez toutes les informations qui s'y trouvent ? Si c'est une table à 10
Pour ne pas tout traiter d'un coup, on extrait cette réponse ligne par ligne, c'est-
<?php
$donnees = $reponse->fetch();
?>
$donnees est un array qui contient champ par champ les valeurs de la première
entrée.
Page 8
Par exemple, si vous vous intéressez au champ console, vous utiliserez
l'array $donnees['console'].
Il faut faire une boucle pour parcourir les entrées une à une. Chaque fois que
En cas d’erreur
<?php
try
catch(Exception $e)
{ ?> <p>
Page 9
Le possesseur de ce jeu est : <?php echo $donnees['possesseur']; ?>, et il le vend
<?php
try
catch(Exception $e)
{ ?> <p>
Page 11
Le possesseur de ce jeu est : <?php echo $donnees['possesseur']; ?>, et il le vend
</p>
<?php }
?>
Exécution
Jeu : Sonic
Le possesseur de ce jeu est : Patrick, et il le vend à 2 euros !
Ce jeu fonctionne sur Megadrive et on peut y jouer à 1 au maximum
Patrick a laissé ces commentaires sur Sonic : Pour moi, le meilleur jeu du
monde !
Page 11
Michel a laissé ces commentaires sur Super Smash Bros Melee : Un jeu de
baston délirant !
Etc….
On commence par l'entrée n°1, puis l'entrée n°2, etc. Chaque fois qu'on fait une
$donnees est un array renvoyé par le fetch(). Chaque fois qu'on fait une boucle,
fetch va chercher dans $reponse l'entrée suivante et organise les champs dans
l'array $donnees.
Page 12
cette ligne fait deux choses à la fois :
elle récupère une nouvelle entrée et place son contenu dans $donnees ;
Le fetch renvoie faux (false) dans $donnees lorsqu'il est arrivé à la fin des
données, c'est-à-dire que toutes les entrées ont été passées en revue. Dans ce cas,
<?php
try
{
$bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
}
catch(Exception $e)
{
die('Erreur : '. $e->getMessage());
}
$reponse->closeCursor();
?>
Page 13
9. Les critères de sélection
Vous allez voir qu'en modifiant nos requêtes SQL, il est possible de filtrer et trier
très facilement vos données. Nous allons nous intéresser ici aux mots-clés
suivants du langage SQL :
WHERE ;
ORDER BY ;
LIMIT.
WHERE
Supposons par exemple que je veuille lister uniquement les jeux appartenant à
Patrick.
<?php
try
{
$bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
}
catch(Exception $e)
{
die('Erreur : '.$e->getMessage());
}
Page 14
$reponse = $bdd->query('SELECT nom, possesseur FROM jeux_video WHERE
possesseur=\'Patrick\'');
?>
ORDER BY
ORDER BY nous permet d'ordonner nos résultats. Nous pourrions ainsi classer
les résultats en fonction de leur prix ! La requête SQL serait :
Page 15
Application :
<?php
try
{
$bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
}
catch(Exception $e)
{
die('Erreur : '.$e->getMessage());
}
$reponse->closeCursor();
?>
LIMIT
LIMIT nous permet de ne sélectionner qu'une partie des résultats (par exemple
les 20 premiers). C'est très utile lorsqu'il y a beaucoup de résultats et que vous
souhaitez les paginer (c'est-à-dire par exemple afficher les 30 premiers résultats
sur la page 1, les 30 suivants sur la page 2, etc).
Page 16
À la fin de la requête, il faut ajouter le mot-cléLIMITsuivi de deux nombres
séparés par une virgule. Par exemple :
<?php
try
{
$bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
}
catch(Exception $e)
{
die('Erreur : '.$e->getMessage());
}
$reponse->closeCursor();
?>
Au lieu de toujours afficher les jeux de Patrick, on aimerait que cette requête soit
demande de l'utilisateur !
Page 17
Nous pourrions être tentés de concaténer la variable dans la requête, comme
ceci :
<?php
$reponse = $bdd->query('SELECT nom FROM jeux_video WHERE possesseur=\''
. $_GET['possesseur'] . '\'');
?>
Bien que ce code fonctionne, c'est l'illustration parfaite de ce qu'il ne faut pas
faire et que pourtant beaucoup de sites font encore. En effet, si la
variable$_GET['possesseur'] a été modifiée par un visiteur (et nous savons à quel
point il ne faut pas faire confiance à l'utilisateur !), il y a un gros risque de faille
de sécurité qu'on appelle injection SQL. Un visiteur pourrait s'amuser à insérer
une requête SQL au milieu de la vôtre et potentiellement lire tout le contenu de
votre base de données, comme par exemple la liste des mots de passe de vos
utilisateurs.
Nous allons utiliser un autre moyen plus sûr d'adapter nos requêtes en
Classe PDO
PDO {
public __construct ( string $dsn [, string $username [, string $passwd [, array
$options ]]] )
public beginTransaction ( void ) : bool
public commit ( void ) : bool
Page 18
public errorCode ( void ) : string
public errorInfo ( void ) : array
public exec ( string $statement ) : int
public getAttribute ( int $attribute ) : mixed
public static getAvailableDrivers ( void ) : array
public inTransaction ( void ) : bool
public lastInsertId ([ string $name = NULL ] ) : string
public prepare ( string $statement [, array $driver_options = array() ] ) :
PDOStatement
public query ( string $statement ) : PDOStatement
public quote ( string $string [, int $parameter_type = PDO::PARAM_STR ] ) :
string
public rollBack ( void ) : bool
public setAttribute ( int $attribute , mixed $value ) : bool
}
PDO::prepare
Page 19
l'un ou l'autre. Utilisez ces paramètres pour lier les entrées utilisateurs, ne les
incluez pas directement dans la requête.
Vous devez inclure un marqueur avec un nom unique pour chaque valeur que
vous souhaitez passer dans la requête lorsque vous appelez
PDOStatement::execute(). Vous ne pouvez pas utiliser un marqueur avec deux
noms identiques dans une requête préparée, à moins que le mode émulation ne
soit actif.
<?php
$req = $bdd->prepare('SELECT nom FROM jeux_video WHERE possesseur = ?');
?>
Au lieu d'exécuter la requête avec query() comme la dernière fois, on appelle ici
prepare().
La requête est alors prête, sans sa partie variable. Maintenant, nous allons
exécuter la requête en appelant execute et en lui transmettant la liste des
paramètres :
<?php
$req = $bdd->prepare('SELECT nom FROM jeux_video WHERE possesseur = ?');
$req->execute(array($_GET['possesseur']));
?>
La requête est alors exécutée à l'aide des paramètres que l'on a indiqués sous
forme d'array.
S'il y a plusieurs marqueurs, il faut indiquer les paramètres dans le bon ordre :
<?php
$req = $bdd->prepare('SELECT nom FROM jeux_video WHERE possesseur = ?
AND prix <= ?');
Page 21
$req->execute(array($_GET['possesseur'], $_GET['prix_max']));
?>
Construire une page capable de lister les jeux appartenant à une personne et
dont le prix ne dépasse pas une certaine somme :
<?php
try
{
$bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
}
catch(Exception $e)
{
die('Erreur : '.$e->getMessage());
}
echo '<ul>';
while ($donnees = $req->fetch())
{
echo '<li>' . $donnees['nom'] . ' (' . $donnees['prix'] . ' EUR)</li>';
}
echo '</ul>';
$req->closeCursor();
?>
Bien que la requête soit « sécurisée » (ce qui élimine les risques d'injection SQL),
il faudrait quand même vérifier que $_GET['prix_max'] contient bien un nombre
et qu'il est compris dans un intervalle correct. Vous n'êtes donc pas dispensés
d'effectuer des vérifications supplémentaires si vous estimez que cela est
nécessaire.
Page 21
Essayez d'appeler cette page (que l'on nommera par exemple selection_jeux.php)
en modifiant les valeurs des paramètres. Vous allez voir que la liste des jeux qui
ressort change en fonction des paramètres envoyés !
<?php
$_GET['prix_max']));
?>
Les points d'interrogation ont été remplacés par les marqueurs nominatifs
:possesseur et : prixmax.
Cette fois-ci, ces marqueurs sont remplacés par les variables à l'aide d'un array
associatif. Quand il y a beaucoup de paramètres, cela permet parfois d'avoir plus
de clarté. De plus, contrairement aux points d'interrogation, nous ne sommes
cette fois plus obligés d'envoyer les variables dans le même ordre que la requête.
Lorsqu'une requête SQL « plante », bien souvent PHP vous dira qu'il y a eu une
erreur à la ligne du fetch :
Page 22
Ce n'est pas la ligne du fetch qui est en cause : c'est souvent vous qui avez mal
écrit votre requête SQL quelques lignes plus haut.
Pour afficher des détails sur l'erreur, il faut activer les erreurs lors de la
connexion à la base de données via PDO.
soit la ligne ?
<?php
$bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
?>
<?php
$bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '',
array(PDO :: ATTR_ERRMODE => PDO :: ERRMODE_EXCEPTION));
?>
Désormais, toutes vos requêtes SQL qui comportent des erreurs les afficheront
avec un message beaucoup plus clair.
<?php
$reponse = $bdd->query('SELECT champinconnu FROM jeux_video');
?>
Nous avons vu que l'on pouvait facilement récupérer des informations de notre
base de données. Nous avons également pu constater que le langage SQL est très
puissant car il propose de nombreux critères de sélection et de tri (WHERE,
ORDER BY, etc.).
Page 23
a. INSERT : ajouter des données
Pour rajouter une entrée, vous aurez besoin de connaître la requête SQL. En voici
une par exemple qui ajoute un jeu :
Les nombres (tels que 45 et 50 ici) n'ont pas besoin d'être entourés d'apostrophes.
Seules les chaînes de caractères les nécessitent.
Vous remarquerez que pour le premier champ (ID), j'ai laissé des apostrophes
vides. C'est voulu : le champ a la propriété auto_increment, MySQL mettra donc
le numéro d'ID lui-même. On pourrait même se passer du champ ID dans la
requête :
Enfin, si vous le désirez, sachez que vous n'êtes pas obligés de lister les noms des
champs en premier ; cette requête marche tout aussi bien (mais elle est moins
claire) :
INSERT INTO jeux_video VALUES('', 'Battlefield 1942', 'Patrick', 'PC', 45, 50,
'2nde guerre mondiale')
Il faut lister les valeurs pour tous les champs sans exception (ID compris) dans le
bon ordre.
Application en PHP
Page 24
<?php
try
{
$bdd = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root', '');
}
catch(Exception $e)
{
die('Erreur : '.$e->getMessage());
}
ce code ajoute une entrée dans la BDD pour le jeu « Battlefield 1942 »,
appartenant à « Patrick », qui fonctionne sur « PC », qui coûte 45 euros, etc.
Si on choisit d'utiliser une requête préparée (ce que je vous recommande si vous
souhaitez insérer des variables), le fonctionnement est en fait exactement le
même que dans le chapitre précédent
<?php
Page 25
$req = $bdd->prepare('INSERT INTO jeux_video(nom, possesseur, console, prix,
nbre_joueurs_max, commentaires) VALUES(:nom, :possesseur, :console, :prix,
:nbre_joueurs_max, :commentaires)');
$req->execute(array(
'nom' => $nom,
'possesseur' => $possesseur,
'console' => $console,
'prix' => $prix,
'nbre_joueurs_max' => $nbre_joueurs_max,
'commentaires' => $commentaires
));
On a utilisé ici des marqueurs nominatifs. On a créé l'array sur plusieurs lignes :
c'est autorisé, et c'est surtout bien plus lisible.
Les variables telles que $nom et $possesseur doivent avoir été définies
précédemment. Généralement, on récupèrera des variables de$_POST(issues
d'un formulaire) pour insérer une entrée dans la base de données. Nous
découvrirons un cas pratique dans le TP suivant.
Application en PHP
<?php
$bdd->exec('UPDATE jeux_video SET prix = 10, nbre_joueurs_max = 32 WHERE
nom = \'Battlefield 1942\'');
?>
Notez que cet appel renvoie le nombre de lignes modifiées. Essayez de récupérer
cette valeur dans une variable et de l'afficher, par exemple comme ceci :
Page 26
<?php
$nb_modifs = $bdd->exec('UPDATE jeux_video SET possesseur = \'Florent\'
WHERE possesseur = \'Michel\'');
echo $nb_modifs . ' entrées ont été modifiées !';
?>
Cela affichera quelque chose comme :13 entrées ont été modifiées !
<?php
$req->execute(array(
));?>
Si vous oubliez le WHERE, toutes les entrées seront supprimées. Cela équivaut à
vider la table.
Je vous laisse essayer cette requête en PHP. Vous pouvez là encore passer par
exec()si vous voulez exécuter une requête bien précise, ou bien utiliser une
requête préparée si votre requête dépend de variables.
Page 27
15. Traiter les erreurs SQL
Lorsqu'il s'est produit une erreur SQL, la page affiche le plus souvent l'erreur
suivante :
Cette erreur survient lorsque vous voulez afficher les résultats de votre requête,
généralement dans la boucle while ($donnees = $reponse->fetch()).
Repérez la requête qui selon vous plante (certainement celle juste avant la boucle
while), et demandez d'afficher l'erreur s'il y en a une, comme ceci :
<?php
$reponse = $bdd->query('SELECT nom FROM jeux_video') or die(print_r($bdd-
>errorInfo()));
Page 28