TD2 – La persistance des données en PHP Base de données, PDO
Dans le TD1 vous avez appris à créer des classes et à instancier des objets de ces classes. Mais, comme vous l’avez constaté, la durée de vie des objets ainsi créés ne dépassait pas la durée de l’exécution du programme.
Dans ce TD, nous allons apprendre à rendre les objets persistants, en les sauvegardant dans une base de données. Ainsi, il sera possible de retrouver les objets d’une visite à l’autre du site web.
Connexion à la base de données
Les bases de PhpMyAdmin
-
Connectez vous à votre base de données MySQL, à l’aide de l’interface PhpMyAdmin http://webinfo.iutmontp.univ-montp2.fr/my Le login est votre login IUT et votre mot de passe initial est votre numéro INE (avec les lettres en majuscule).
Si cela ne marche pas, c’est que vous n’êtes probablement pas inscrit administrativement. Dans ce cas, demandez à votre chargé de TD ou allez voir le service informatique (même bureau que le secrétariat au bâtiment 4). -
Changez votre mot de passe (Page d’accueil > Paramètres généraux > Modifier le mot de passe) et reconnectez-vous. Si vous n’arrivez pas à vous connecter après avoir changé le mot de passe, essayer avec un autre navigateur ou bien videz le cache du navigateur (
Ctrl+F5
).Attention : N’utilisez pas un de vos mots de passe usuels, car nous allons bientôt écrire ce mot de passe dans un fichier qui sera sans doute vu par le professeur ou votre voisin.
Donc vous avez deux possibilités :- (recommandé) Créez un mot de passe aléatoire à l’aide de https://www.random.org/passwords/ par exemple. Écrivez dès maintenant ce mot de passe dans un fichier.
- Ou choisissez quelque chose de simple et de pas secret.
-
Créez une table
utilisateur
(sans majuscule) possédant 3 champs :loginBaseDeDonnees
de typeVARCHAR
et de longueur maximale 64, défini comme la clé primaire (Index :Primary
)nomBaseDeDonnees
de typeVARCHAR
est de longueur maximale 64.prenomBaseDeDonnees
de typeVARCHAR
est de longueur maximale 64.
Important : Pour faciliter la suite du TD, mettez à la création de toutes vos tables
InnoDB
comme moteur de stockage, etutf8_general_ci
comme interclassement (c’est l’encodage des données, et donc des accents, caractères spéciaux…).Attention : Les noms des champs sont comme des noms de variables, ils ne doivent pas contenir d’accents. Par ailleurs, et contrairement à Oracle, MySQL est sensible à la casse (minuscules/majuscules).
-
Insérez des données en utilisant l’onglet
Insérer
de PhpMyAdmin. -
Dans la suite du TD, pensez à systématiquement tester vos requêtes SQL dans PhpMyAdmin avant de les inclure dans vos pages PHP.
Fichier de configuration en PHP
Pour avoir un code portable, il est préférable de séparer les informations du serveur du reste du code PHP.
-
Commencez par créer un dossier
tds-php/TD2
dans l’explorateur de fichier, puis ouvrez ce dossier dans PHPStorm. -
Créez un fichier
ConfigurationBaseDeDonnees.php
. Ce fichier contiendra une classeConfigurationBaseDeDonnees
possédant un attribut statique$configurationBaseDeDonnees
comme suit (changez bien sûr lesa_remplir
).Notes :
- Où doit-on enregistrer une page Web ? (Souvenez-vous du TD précédent)
- Qu’est-ce qu’un attribut ou une méthode statique ? (Cours de Programmation Orientée Objet de l’an dernier ; voir aussi les compléments)
<?php class ConfigurationBaseDeDonnees { static private array $configurationBaseDeDonnees = array( // Le nom d'hote est webinfo a l'IUT // ou localhost sur votre machine // // ou webinfo.iutmontp.univ-montp2.fr // pour accéder à webinfo depuis l'extérieur 'nomHote' => 'a_remplir', // A l'IUT, vous avez une base de données nommee comme votre login // Sur votre machine, vous devrez creer une base de données 'nomBaseDeDonnees' => 'a_remplir', // À l'IUT, le port de MySQL est particulier : 3316 // Ailleurs, on utilise le port par défaut : 3306 'port' => 'a_remplir', // A l'IUT, c'est votre login // Sur votre machine, vous avez surement un compte 'root' 'login' => 'a_remplir', // A l'IUT, c'est le même mdp que PhpMyAdmin // Sur votre machine personelle, vous avez creez ce mdp a l'installation 'motDePasse' => 'a_remplir' ); static public function getLogin() : string { // L'attribut statique $configurationBaseDeDonnees // s'obtient avec la syntaxe ConfigurationBaseDeDonnees::$configurationBaseDeDonnees // au lieu de $this->configurationBaseDeDonnees pour un attribut non statique return ConfigurationBaseDeDonnees::$configurationBaseDeDonnees['login']; } } ?>
-
Pour tester notre classe
ConfigurationBaseDeDonnees
, créons un fichiertestConfigurationBaseDeDonnees.php
que l’on ouvrira dans le navigateur.Souvenez-vous le TD dernier : Quelle est la bonne et la mauvaise URL pour ouvrir une page PHP ?
<?php // On inclut les fichiers de classe PHP pour pouvoir se servir de la classe ConfigurationBaseDeDonnees. // require_once évite que ConfigurationBaseDeDonnees.php soit inclus plusieurs fois, // et donc que la classe ConfigurationBaseDeDonnees soit déclaré plus d'une fois. require_once 'ConfigurationBaseDeDonnees.php'; // On affiche le login de la base de donnees echo ConfigurationBaseDeDonnees::getLogin(); ?>
-
Complétez
ConfigurationBaseDeDonnees.php
avec des méthodes statiquesgetNomHote()
,getPort()
,getNomBaseDeDonnees()
etgetPassword()
. Testez ces méthodes danstestConfigurationBaseDeDonnees.php
.Remarque : Notez qu’en PHP, on appelle une méthode statique à partir du nom de la classe comme en Java, mais en utilisant
::
au lieu du.
en Java. Souvenez-vous que les méthodes dynamiques (c’est-à-dire passtatic
) s’appellent avec->
en PHP. -
Enregistrez votre travail à l’aide de
git add
etgit commit
. Nous comptons sur vous pour penser à faire cet enregistrement régulièrement.
Initialiser un objet PDO
Pour se connecter à une base de données en PHP on utilise une classe fournie
avec PHP qui s’appelle PDO
(Php Data Object). Cette classe va nous
fournir de nombreuses méthodes très utiles pour manipuler n’importe quelle base
de donnée.
- Commençons par établir une connexion à la base de données. Créez un fichier
ConnexionBaseDeDonnees.php
déclarant une classeConnexionBaseDeDonnees
, qui possédera- un attribut
private PDO $pdo
, - un constructeur sans argument qui ne fait rien pour l’instant (à générer avec PhpStorm),
- un accesseur (getter)
getPdo()
à l’attribut$pdo
(à générer avec PhpStorm).
- un attribut
-
Dans le constructeur, nous allons initialiser l’attribut
$pdo
en lui assignant un objetPDO
. Procédons par étapes :-
Pour créer la connexion à notre base de donnée, il faut utiliser le constructeur de
PDO
de la façon suivantenew PDO("mysql:host=$nomHote;port=$port;dbname=$nomBaseDeDonnees",$login,$motDePasse);
Stockez ce nouvel objet
PDO
dans l’attribut$pdo
. -
Le code précédent a besoin que les variables
$nomHote
,$port
,$nomBaseDeDonnees
,$login
et$motDePasse
contiennent les chaînes de caractères correspondant à l’hôte, au nom, au login et au mot de passe de notre base de données. Créez donc ces variables avant lenew PDO
en récupérant les informations à l’aide des fonctions de la classeConfigurationBaseDeDonnees
. -
Comme notre classe
ConnexionBaseDeDonnees
dépend deConfigurationBaseDeDonnees.php
, ajoutez unrequire_once 'ConfigurationBaseDeDonnees.php'
au début du fichier. -
Testons dès à présent notre nouvelle classe. Créez le fichier
testConnexionBaseDeDonnees.php
suivant. Vérifiez que l’exécution detestConnexionBaseDeDonnees.php
ne donne pas de messages d’erreur.<?php require_once "ConnexionBaseDeDonnees.php"; // On affiche un attribut de PDO pour vérifier que la connexion est bien établie. // Cela renvoie par ex. "webinfo.iutmontp.univ-montp2.fr via TCP/IP" // mais surtout pas de message d'erreur // SQLSTATE[HY000] [1045] Access denied for user ... (mauvais mot de passe) // ou // SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed (mauvais nom d'hôte) $model = new ConnexionBaseDeDonnees(); echo $model->getPdo()->getAttribute(PDO::ATTR_CONNECTION_STATUS); ?>
-
Patron de conception Singleton
Comme cela n’a pas de sens d’avoir plusieurs connexions à la base de données, nous allons utiliser le patron de conception Singleton. Il sert à assurer qu’il n’y ait qu’une et une seule instance possible de la classe ConnexionBaseDeDonnees
dans l’application (et donc une seule connexion).
Voici le squelette d’un singleton :
class ConnexionBaseDeDonnees {
private static $instance = null;
private PDO $pdo;
public static function getPdo(): PDO {
return ConnexionBaseDeDonnees::getInstance()->pdo;
}
private function __construct () {
// Code du constructeur
}
// getInstance s'assure que le constructeur ne sera
// appelé qu'une seule fois.
// L'unique instance crée est stockée dans l'attribut $instance
private static function getInstance() : ConnexionBaseDeDonnees {
// L'attribut statique $instance s'obtient avec la syntaxe ConnexionBaseDeDonnees::$instance
if (is_null(ConnexionBaseDeDonnees::$instance))
// Appel du constructeur
ConnexionBaseDeDonnees::$instance = new ConnexionBaseDeDonnees();
return ConnexionBaseDeDonnees::$instance;
}
}
Remarque : Quand un attribut est statique, il s’accède par une syntaxe
NomClasse::$nomVar
comme indiqué précédemment.
- Mettez à jour votre classe
ConnexionBaseDeDonnees
pour qu’elle suive le design pattern Singleton. - Mettez à jour
testConnexionBaseDeDonnees.php
et vérifiez que tout marche bien. - Déclarez que l’attribut
$instance
est de typeConnexionBaseDeDonnees
.
L’IDE indique un problème : L’attribut$instance
est initialisé ànull
, qui n’est pas de typeConnexionBaseDeDonnees
en PHP (contrairement à Java), mais de typenull
.
Corrigez ce problème en indiquant le type?ConnexionBaseDeDonnees
pour l’attribut$instance
. En effet,?ConnexionBaseDeDonnees
est un raccourci pour le typeConnexionBaseDeDonnees|null
, qui veut direConnexionBaseDeDonnees
ounull
.
Gestion des erreurs
Nous allons maintenant améliorer la gestion des erreurs de PDO
.
Pour avoir plus de messages d’erreur de PDO
et qu’il gère mieux l’UTF-8,
mettez à jour la connexion dans ConnexionBaseDeDonnees
en remplaçant $this->pdo = new PDO(...);
par
// Connexion à la base de données
// Le dernier argument sert à ce que toutes les chaines de caractères
// en entrée et sortie de MySql soient dans le codage UTF-8
$this->pdo = new PDO("mysql:host=$nomHote;port=$port;dbname=$nomBaseDeDonnees", $login, $motDePasse,
array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
// On active le mode d'affichage des erreurs, et le lancement d'exception en cas d'erreur
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Opérations sur la base de données
Voyons maintenant comment les objets PDO
servent à effectuer des requêtes
SQL. Nous allons nous servir de deux méthodes fournies par PDO
:
- La méthode
query($SQL_request)
de la classePDO
- prend en entrée une requête SQL (chaîne de caractères)
- et renvoie la réponse de la requête dans une représentation interne pas
immédiatement lisible
(un objet
PDOStatement
).
- La
méthode
fetch()
de la classePDOStatement
s’appelle sur les réponses de requêtes et renvoie la réponse de la requête dans un format lisible par PHP. Plus précisément, elle renvoie une entrée SQL formatée comme un tableau. Ce tableau est indexé par les noms des champs de la table de données, et aussi par les numéros des champs. Les valeurs du tableau sont celles de l’entrée SQL.
Faire une requête SQL sans paramètres
Commençons par la requête SQL la plus simple, celle qui lit tous les éléments
d’une table (utilisateur
dans notre exemple) :
SELECT * FROM utilisateur
-
Créez un fichier
lireUtilisateurs.php
-
Incluez le fichier contenant la classe
ConnexionBaseDeDonnees
pour pouvoir se connecter à la base de données. -
Appelez la fonction
query
de l’objetPDO
ConnexionBaseDeDonnees::getPdo()
en lui donnant la requête SQL. Stockez sa réponse dans une variable$pdoStatement
. -
Comme expliqué précédemment, pour lire les réponses à des requêtes SQL, vous pouvez utiliser
$utilisateurFormatTableau = $pdoStatement->fetch()
qui, dans notre exemple, renvoie un tableau avec 6 cases :
loginBaseDeDonnees
,prenomBaseDeDonnees
etnomBaseDeDonnees
(les champs de la base de données).0
,1
et2
qui correspondent aux champs de la base de données dans l’ordre. Ces cases sont donc un peu redondantes.
Utilisez l’un des affichages de débogage (e.g.
var_dump
) pour afficher ce tableau. -
Créez un
$utilisateur
de classeUtilisateur
à l’aide de$utilisateurFormatTableau
en appelant le constructeur. Affichez l’utilisateur en utilisant la méthode adéquate deUtilisateur
. Copiez le fichiertds-php/TD1/Utilisateur.php
danstds-php/TD2
pour pouvoir utiliser la classeUtilisateur
dans le TD2. -
On souhaite désormais afficher tous les utilisateurs dans la base de données. On pourrait faire une boucle
while
surfetch
tant qu’on n’a pas parcouru toutes les entrées de la base de données.Heureusement, il existe une syntaxe simplifiée qui fait exactement cela :
foreach($pdoStatement as $utilisateurFormatTableau){ // ... }
Note :
- chaque tour de boucle agit comme si on avait fait un fetch
$utilisateurFormatTableau = $pdoStatement->fetch()
- on peut faire foreach car PDOStatement implémente l’interface Traversable.
C’est similaire à Java qui permettait la boucle
for(xxx : yyy)
pour les objets implémentant l’interfaceIterable
.
Utilisez la boucle
foreach
danslireUtilisateurs.php
pour afficher tous les utilisateurs. - chaque tour de boucle agit comme si on avait fait un fetch
-
Avez-vous pensé à enregistrer régulièrement votre travail sous Git ?
Nous allons maintenant isoler le code qui retourne tous les utilisateurs et en faire une méthode de Utilisateur
.
- Isolez le code qui construit l’objet
Utilisateur
à partir du tableau donné parfetch
(e.g.$utilisateurFormatTableau
) dans une méthodepublic static function construireDepuisTableauSQL(array $utilisateurFormatTableau) : Utilisateur { // ... }
-
Créez une fonction statique
recupererUtilisateurs()
dans la classeUtilisateur
qui ne prend pas d’arguments et renvoie le tableau d’objets de la classeUtilisateur
correspondant à la base de données.Rappel : On peut rajouter facilement un élément “à la fin” d’un tableau avec
$tableau[] = "Nouvelle valeur";
-
Mettez à jour
lireUtilisateurs.php
pour appeler directementrecupererUtilisateurs()
. - Maintenant que vous avez bien compris où les noms de colonnes (
loginBaseDeDonnees
,prenomBaseDeDonnees
, …) de la tableutilisateur
interviennent dans le tableau$utilisateurFormatTableau
, nous allons leur redonner des noms plus classiques :- Changer les noms des colonnes pour
login
,prenom
etnom
. Pour ceci, dans PhpMyAdmin, cliquez sur l’onglet “Structure” de la tableutilisateur
, puis “Modifier” sur chaque colonne. - Modifiez le code PHP à l’endroit où interviennent ces noms de colonnes.
- Changer les noms des colonnes pour
Format de retour de fetch()
Rappelons que la
méthode fetch($fetchStyle)
s’appelle sur les réponses de requêtes et renvoie
la réponse de la requête dans un format lisible par PHP.
Le choix du format se fait avec la
variable $fetchStyle
. Les formats les plus communs sont :
-
PDO::FETCH_ASSOC
: Chaque entrée SQL est un tableau indexé par les noms des champs de la table de la base de données ; -
PDO::FETCH_NUM
: Chaque entrée SQL est un tableau indexé par le numéro de la colonne commençant à 0 ; -
PDO::FETCH_BOTH
(valeur par défaut si on ne donne pas d’argument$fetchStyle
) : combinaison dePDO::FETCH_ASSOC
etPDO::FETCH_NUM
. Ce format retourne un tableau indexé par les noms de colonnes et aussi par les numéros de colonnes, commençant à l’index 0, comme retournés dans le jeu de résultats -
PDO::FETCH_OBJ
: Chaque entrée SQL est un objet dont les noms d’attributs sont les noms des champs de la table de la base de données ; -
PDO::FETCH_CLASS
: De même quePDO::FETCH_OBJ
, chaque entrée SQL est un objet dont les noms d’attributs sont les noms des champs de la table de la base de données. Cependant, on peut dans ce cas spécifier le nom de la classe des objets. Pour ce faire, il faut avoir au préalable déclaré le nom de la classe avec la commande suivante :$pdoStatement->setFetchMode( PDO::FETCH_CLASS, 'class_name');
Note : Ce format qui semble très pratique a malheureusement un comportement problématique :
- il crée d’abord une instance de la classe demandée (sans passer par le constructeur !) ;
- il écrit les attributs correspondants aux champs de la base de données (même s’ils sont privés ou n’existent pas !) ;
- puis il appelle le constructeur sans arguments.
Dans les TDs, nous vous recommandons d’utiliser au choix :
- le format par défaut
PDO::FETCH_BOTH
en appelantfetch()
sans arguments, - le format
PDO::FETCH_ASSOC
pour ne pas avoir de cases redondantes (e.gloginBaseDeDonnees
et0
).
Dans ce cas, appelez$pdoStatement->setFetchMode(PDO::FETCH_ASSOC)
avant d’appelerfetch()
.