TD3 – Requêtes préparées et association de classes SQL JOIN
Ce TD3 est le prolongement du TD2 sur l’enregistrement des données dans une BDD
en utilisant la classe PDO
de PHP. Nous poursuivons par le concept très
important de requêtes préparées. Puis nous coderons des associations entre
plusieurs tables de la BDD.
Nous vous invitons toujours à utiliser les différentes commandes
status/log/add/commit
de git
pour savoir où vous en êtes et enregistrer vos
modifications.
Les injections SQL
Exemple d’injection SQL
Imaginez un site Web qui, pour connecter un utilisateur, exécute la requête SQL suivante et accepte la connexion dès que la requête renvoie au moins une réponse.
SELECT uid FROM Users WHERE name = '$nom' AND password = '$motDePasse';
Un utilisateur malveillant pourrait taper les renseignements suivants :
- Utilisateur :
Dupont';--
- Mot de passe : n’importe lequel
La requête devient :
SELECT uid FROM Users WHERE name = 'Dupont'; -- ' AND password = 'mdp';
ce qui est équivalent à
SELECT uid FROM Users WHERE name = 'Dupont';
L’attaquant peut alors se connecter sous l’utilisateur Dupont avec n’importe quel mot de passe. Cette attaque du site Web s’appelle une injection SQL.
Vous aurez un exercice à la fin du TD pour simuler une injection SQL.
Source : https://fr.wikipedia.org/wiki/Injection_SQL
Les requêtes préparées
Imaginez que nous ayons codé une fonction recupererUtilisateurParLogin($login)
comme suit
function recupererUtilisateurParLogin(string $login) {
$sql = "SELECT * from utilisateur WHERE login='$login'";
$pdoStatement = ConnexionBaseDeDonnees::getPdo()->query($sql);
return $pdoStatement->fetch();
}
Cette fonction marche, mais pose un gros problème de sécurité ; elle est vulnérable aux injections SQL et un utilisateur pourrait faire comme dans l’exemple précédent pour exécuter le code SQL qu’il souhaite.
Pour empêcher les injections SQL, nous allons utiliser une fonctionnalité qui s’appelle les requêtes préparées et qui est fournie par PDO. Voici comment les requêtes préparées fonctionnent :
-
On met un tag
:nomTag
en lieu de la valeur à remplacer dans la requête SQL -
On doit “préparer” la requête avec la commande
prepare($requeteSql)
-
Puis utiliser un tableau pour associer des valeurs aux noms des tags des variables à remplacer :
$values = array("nomTag" => "une valeur"); // Sans deux points devant nomTag
-
Et exécuter la requête préparée avec
execute($values)
-
On peut alors récupérer les résultats comme précédemment (e.g. avec
fetch()
)
Voici toutes ces étapes regroupées dans une fonction :
function recupererUtilisateurParLogin(string $login) : Utilisateur {
$sql = "SELECT * from utilisateur WHERE login = :loginTag";
// Préparation de la requête
$pdoStatement = ConnexionBaseDeDonnees::getPdo()->prepare($sql);
$values = array(
"loginTag" => $login,
//nomdutag => valeur, ...
);
// On donne les valeurs et on exécute la requête
$pdoStatement->execute($values);
// On récupère les résultats comme précédemment
// Note: fetch() renvoie false si pas d'utilisateur correspondant
$utilisateurFormatTableau = $pdoStatement->fetch();
return Utilisateur::construireDepuisTableauSQL($utilisateurFormatTableau);
}
Remarque : Il existe une autre solution pour associer une à une les valeurs
aux variables d’une requête préparée avec la fonction
bindParam()
de la classe
PDO (qui permet de donner le type de la valeur). Cependant, nous vous conseillons
d’utiliser systématiquement la syntaxe avec un tableau execute($values)
.
-
Copiez/collez dans un nouveau dossier TD3 les fichiers
ConfigurationBaseDeDonnees.php
,ConnexionBaseDeDonnees.php
,Utilisateur.php
etlireUtilisateurs.php
. -
Copiez la fonction précédente
recupererUtilisateurParLogin
dans la classeUtilisateur
en la déclarant publique et statique. -
Testez la fonction
recupererUtilisateurParLogin
dans un nouveau fichiertestRequetePrepare.php
.Remarque : Vous aurez sans doute une erreur
Class "ConnexionBaseDeDonnees" not found
. Où inclureConnexionBaseDeDonnees.php
: dansUtilisateur.php
ou danscreerUtilisateur.php
?
Règle simple : chaque fichier doit inclure les classes dont il a besoin. CommeUtilisateur.php
a besoin de la classeConnexionBaseDeDonnees
(à cause de l’instructionConnexionBaseDeDonnees::getPdo()
), c’est au début deUtilisateur.php
qu’il faut fairerequire_once "ConnexionBaseDeDonnees.php";
. -
On souhaite que
recupererUtilisateurParLogin
renvoienull
s’il n’existe pas d’utilisateur de login$login
. Mettez à jour le code et la déclaration de type. Testez votre code.
Désormais, toutes les requêtes SQL doivent être codées en utilisant des
requêtes préparées, sauf éventuellement des requêtes SQL sans variable comme
SELECT * FROM utilisateur
.
-
Créez une fonction
public function ajouter() : void
dans la classeUtilisateur
qui insère l’utilisateur courant ($this
) dans la BDD. On vous rappelle la syntaxe SQL d’une insertion :INSERT INTO table_name (column1, column2, ...) VALUES (value1, value2, ...)
Attention : La requête
INSERT INTO
ne renvoie pas de résultats ; il ne faut donc pas faire defetch()
sous peine d’avoir une erreurSQLSTATE[HY000]: General error
. -
Testez cette fonction dans
testRequetePrepare.php
en créant un objet de classeUtilisateur
et en l’enregistrant.
Remarque : Le nom de la fonction ajouter()
peut prêter à confusion. En effet, lorsqu’on voit une fonction ajouter()
dans une classe Utilisateur
, on ne s’attend pas forcément à ce qu’il y ait un enregistrement en BD.
Pour le moment nous allons garder le nom de cette méthode tel quel, car dans quelques semaines le code de celle-ci sera migré ailleurs (dans une classe de persistance)
et le nom ajouter()
prendra tout son sens.
Branchons maintenant notre enregistrement d’utilisateur dans la BDD au formulaire de création d’utilisateur du TD1 :
-
Copiez dans le dossier TD3 les fichiers
creerUtilisateur.php
etformulaireCreationUtilisateur.html
du TD1. -
Modifiez la page
creerUtilisateur.php
de sorte qu’elle sauvegarde l’objetUtilisateur
reçu (en GET ou POST, au choix). -
Testez l’insertion grâce au formulaire
formulaireCreationUtilisateur.html
. -
Vérifiez dans PhpMyAdmin que les utilisateurs sont bien sauvegardés.
-
Essayez d’ajouter un utilisateur dont un champ contient un guillemet simple
'
, par exemple un nom"D'Artagnan"
. Est-ce qu’elle a bien été sauvegardée ? Si ce n’est pas le cas, c’est sûrement que vous n’avez pas utilisé les requêtes préparées.
Utilisateurs et trajets
Vous avez couvert dans le cours R2.01 – Développement orienté objets les diagrammes de classes. Ce type de diagramme est utile pour penser la base de donnée d’une application Web. Voici le nôtre :
Question : Comment implémenteriez-vous l’association conducteur entre utilisateurs et trajets dans la BDD en tenant compte de sa multiplicité ?
Notre solution (surlignez le texte caché à droite) : Comme il n’y a qu’un conducteur par trajet, nous allons rajouter un champ conducteurLogin à la table trajet.
Création des tables
La table utilisateur
avec quelques utilisateurs a déjà été créée dans votre PhpMyAdmin. Créez la table trajet
comme suit :
- Créez une table
trajet
avec les champs suivants :id
: INT, clé primaire, qui s’auto-incrémente (voir en dessous)depart
: VARCHAR 64arrivee
: VARCHAR 64date
: DATEprix
: INTconducteurLogin
: VARCHAR 64nonFumeur
: BOOLEAN
Note : On souhaite que le champ primaire
id
s’incrémente à chaque nouvelle insertion dans la table. Pour ce faire, cochez la caseA_I
(auto-increment) pour le champid
.Note : Observez qu’à l’enregistrement de votre table dans PhpMyAdmin le type BOOLEAN est remplacé par
tinyint
, où0
correspond à"faux" et
1` correspond à “vrai”.Important : Avez-vous bien pensé à
InnoDB
etutf8_general_ci
comme précédemment ? - Insérez quelques trajets en prenant soin de ne pas remplir la case
id
(pour que l’auto-incrément marche) et en mettant dansconducteurLogin
un login d’utilisateur valide (pour éviter des problèmes par la suite).
Lecture des tables
Au niveau du PHP, nous vous fournissons la classe de base Trajet.php
.
Elle est assez semblable à la classe Utilisateur.php
que vous avez déjà codée, à quelques détails près :
- l’attribut
$date
est stocké en tant qu’objet de la classe PHPDateTime
; - l’attribut
$id
peut êtrenull
pour indiquer que l’on ne connait pas encore l’identifiant d’un trajet ; - l’attribut
$conducteur
est stocké en tant qu’objet de la classe PHPUtilisateur
;
-
Enregistrez la classe suivante :
Trajet.php
. -
En vous inspirant de
lireUtilisateurs.php
, créezlireTrajets.php
qui liste les trajets. Vous allez devoir modifier la fonctionTrajet::construireDepuisTableauSQL
car- la date renvoyée par MySQL est un
string
, alors queTrajet
attend unDateTime
. Pour transformer une datestring
enDateTime
, utilisez le constructeurnew DateTime($dateString)
. - MySQL ne renvoie que le login du conducteur tandis que
Trajet
attend unUtilisateur
. Utilisez la méthodeUtilisateur::recupererUtilisateurParLogin
. - MySQL renvoie le booléen
nonFumeur
comme un entier0
ou1
. Par chance, PHP converti automatiquement les entiers en booléen donc il n’y a rien à faire.
- la date renvoyée par MySQL est un
Contrainte sur le conducteur
On souhaite que le champ trajet.conducteurLogin
corresponde à tout moment au
login utilisateur.login
d’un conducteur existant. Vous souvenez-vous quelle
est la fonctionnalité des bases de données qui permet ceci ?
Réponse (surlignez le texte caché à droite) : Il faut utiliser des clés étrangères.
Voici les étapes pour faire ce lien :
-
À l’aide de l’interface de PhpMyAdmin, faites de
trajet.conducteurLogin
un index.Aide : Dans l’onglet
Structure
de la tabletrajet
, cliquez sur l’icône de l’actionindex
en face du champconducteurLogin
.Plus de détails : Dire que le champ
conducteurLogin
est un index revient à dire à MySql que l’on veut trouver rapidement les lignes qui ont unconducteurLogin
donné. Du coup, MySql va construire une structure de donnée pour permettre cette recherche rapide. Une clé étrangère est nécessairement un index, car on a besoin de ce genre de recherches pour tester rapidement la contrainte de clé étrangère. -
Ajoutez la contrainte de clé étrangère entre
trajet.conducteurLogin
etutilisateur.login
. Pour ceci, allez dans l’ongletStructure
de la tabletrajet
et cliquez surVue relationnelle
pour accéder à la gestion des clés étrangères.Nous allons utiliser le comportement
ON DELETE CASCADE
pour qu’une association soit supprimée si la clé étrangère est supprimée, et le comportementON UPDATE CASCADE
pour qu’une association soit mise à jour si la clé étrangère est mise à jour.Attention : Pour supporter les clés étrangères, il faut que le moteur de stockage de toutes vos tables impliqués soit
InnoDB
. Vous pouvez choisir ce paramètre à la création de la table ou le changer après coup dans l’ongletOpérations
.
Création d’un trajet
La création d’un trajet à partir d’un formulaire va nous permettre d’apprendre à gérer le fait qu’un formulaire HTML, une classe PHP et une base de donnée ne stockent pas certaines données de la même façon.
- Nous vous fournissons le formulaire de création de trajets. Enregistrez
formulaireCreationTrajet.html
.
Notez que la date est un<input type="date">
, et que le booléennonFumeur
est un<input type="checkbox">
. -
En vous inspirant de
creerUtilisateur.php
, créezcreerTrajet.php
qui traite les données du formulaire précédent. Les 2 étapes clés sont la création d’un objetTrajet
et l’appel à la méthodeajouter()
deTrajet
. Voici comment faire :- À la création de l’objet
Trajet
- mettez
id
ànull
pour indiquer que vous ne connaissez pas son identifiant ; - le formulaire renvoie une date sous forme de
string
alors que la date du trajet doit être un objetDateTime
. Appliquez la transformation de l’exercice sur la lecture des tables. - le formulaire ne renvoie que le login du conducteur tandis que
Trajet
attend unUtilisateur
. Appliquez la transformation de l’exercice sur la lecture des tables. - le formulaire indique le booléen
nonFumeur
en remplissant, ou pas, la case$_GET["nonFumeur"]
. Utilisez doncisset($_GET["nonFumeur"])
pour lire le booléen envoyé par le formulaire.
- mettez
- Créez la méthode dynamique
ajouter()
deTrajet
.
La requêteINSERT
doit remplir tous les champs saufid
, qui sera rempli automatiquement par MySQL.
Pour le tableau de valeurs donné à la requête préparée- MySQL attend une date
string
. Si$date
est unDateTime
, alors$date->format("Y-m-d")
permet de la convertir enstring
; - MySQL attend seulement le login du conducteur ;
- MySQL stocke le booléen
nonFumeur
comme un entier. Il faut ainsi donner à MySQL1
si le trajet est non-fumeur, ou0
sinon.
- MySQL attend une date
- À la création de l’objet
- Testez l’enchaînement du formulaire de création et de
creerTrajet.php
. Vérifiez dans votre base de données que le trajet est bien créé.
L’association passager
entre utilisateurs et trajets
Dans la base de donnée
Question : Comment implémenteriez-vous l’association passager entre utilisateurs et trajets dans la BDD en tenant compte de ses multiplicités ?
Réponse (surlignez le texte caché à droite) : Comme la relation passager est non bornée (on ne limite pas le nombre d’utilisateurs d’un trajet et inversement), on utilise une table de jointure.
Nous choisissons donc de créer une table passager
qui contiendra deux champs :
- l’identifiant INT
trajetId
d’un trajet et - l’identifiant VARCHAR(64)
passagerLogin
d’un utilisateur.
Pour inscrire un utilisateur à un trajet, il suffit d’écrire la ligne
correspondante dans la table passager
avec leur passagerLogin
et leur
trajetId
.
Question : Quelle est la clé primaire de la table passager
?
Réponse (surlignez à droite) : Le couple (trajetId, passagerLogin). Si vous choisissez trajetId seul comme clé primaire, un trajet aura au plus un passager, et si vous choisissez passagerLogin, chaque utilisateur ne pourra être passager que sur un unique trajet.
-
Créer la table
passager
en utilisant l’interface de PhpMyAdmin.Important : Avez-vous bien pensé à
InnoDB
etutf8_general_ci
comme précédemment ? -
Assurez-vous que vous avez bien le bon couple en tant que clé primaire. Cela se voit dans la section
Index
de l’ongletStructure
. -
Ajoutez la contrainte de clé étrangère entre
passager.trajetId
ettrajet.id
, puis entrepassager.passagerLogin
etutilisateur.login
. Utiliser encore les comportementsON DELETE CASCADE
etON UPDATE CASCADE
pour qu’une association soit mise à jour si la clé étrangère est mise à jour. -
À l’aide de l’interface de PhpMyAdmin, insérer quelques associations pour que la table
passager
ne soit pas vide. -
Vous allez maintenant vous assurer de la bonne gestion des clés étrangères en testant le comportement
ON DELETE CASCADE
. Pour cela :- créez un trajet correspondant à un certain conducteur,
- puis inscrivez des passagers pour ce trajet
- supprimez ensuite le conducteur en question de la table
utilisateur
et vérifiez que les lignes de la tablepassager
précédemment insérées ont bien été supprimées elles aussi.
Au niveau du PHP
Nous allons maintenant pouvoir compléter le code PHP de notre site pour gérer
l’association. Commençons par rajouter des fonctions à nos classes Utilisateur
et Trajet
.
Avant toute chose, vous souvenez-vous comment faire une jointure en SQL ? Si
vous n’êtes pas tout à fait au point sur les différents JOIN
de SQL, vous
pouvez vous rafraîchir la mémoire en lisant
https://www.w3schools.com/sql/sql_join.asp.
-
Codez une fonction
recupererPassagers()
dansTrajet.php
qui retournera un tableau d’Utilisateur
correspondant aux passagers du trajet courant en faisant la requête adéquate. Voici la signature de la fonction./** * @return Utilisateur[] */ private function recupererPassagers() : array { // À coder }
Indices :
- Utiliser une requête à base d’
INNER JOIN
. Une bonne stratégie pour développer la bonne requête est d’essayer des requêtes dans l’onglet SQL de PhpMyAdmin jusqu’à tenir la bonne. - Inspirez-vous de
Utilisateur::recupererUtilisateurs
pour la création d’objetsUtilisateurs
depuis une réponse SQL. - Avez-vous bien utilisé une requête préparée dans
recupererPassagers
?
- Utiliser une requête à base d’
- Nous allons stocker la liste des passagers comme un attribut de la classe
Trajet
.- Ajoutez l’attribut
/** * @var Utilisateur[] */ private array $passagers;
- Mettez à jour le constructeur pour qu’il gère cet attribut avec la valeur par défaut
[]
. - Générez à l’aide de PHPStorm les accesseurs
getPassagers
etsetPassagers
. - Modifiez la fonction
construireDepuisTableauSQL
pour qu’elle instancie le nouveau$trajet
avec une liste des passagers vide, qu’elle récupère les passagers de ce trajet, et qu’elle stocke ces passagers dans l’attribut.
- Ajoutez l’attribut
- Testez votre code en modifiant
lireTrajets.php
pour qu’il affiche pour chaque trajet sa liste des passagers.
Remarque : L’annotation avant la fonction recupererPassagers
/**
* @return Utilisateur[]
*/
est de la documentation PHP (PHPDoc). Elle permet
d’indiquer à l’IDE que la fonction renverra un tableau d’Utilisateur
. PHPDoc
est dans ce cas plus précis que le type de retour de la fonction : array
. Idem pour
/**
* @var Utilisateur[]
*/
qui indique que la variable suivante sera un tableau d’Utilisateur
.
Créez une injection SQL
Nous vous proposons de créer un exemple d’injection SQL.
Mettons en place notre attaque SQL :
- Pour ne pas supprimer une table importante, créons une table
utilisateur2
qui ne craint rien :- allez dans PHPMyAdmin et cliquez sur votre base de donnée (celle dont le nom est votre login à l’IUT)
- Dans l’onglet SQL
Importer
, donnez le fichierutilisateur2.sql
qui créera une tableutilisateur2
avec quelques utilisateurs.
-
Nous vous fournissons le fichier PHP que nous allons attaquer :
formulaireLectureUtilisateur.php
Ce fichier contient un formulaire qui affiche les informations d’un utilisateur étant donné son login.
Testez ce fichier en donnant un login existant.
Lisez le code pour être sûr de bien comprendre le fonctionnement de cette page (et demandez au professeur si vous ne comprenez pas tout !). -
Trouvez ce qu’il faut taper dans le formulaire pour que
recupererUtilisateurParLogin
vide la tableutilisateur2
(SQL Truncate).Aide : Le point clé de ce fichier est que la fonction
recupererUtilisateurParLogin
a été codée sans requête préparée et est vulnérable aux injections SQL.function recupererUtilisateurParLogin(string $login) : ?Utilisateur { $sql = "SELECT * from utilisateur2 WHERE login='$login'"; echo "<p>J'effectue la requête <pre>$sql</pre></p>"; $pdoStatement = ConnexionBaseDeDonnees::getPDO()->query($sql); $utilisateurTableau = $pdoStatement->fetch(); if ($utilisateurTableau !== false) { return Utilisateur::construireDepuisTableauSQL($utilisateurTableau); } return null; }
Deux cas concrets
Pour éviter les radars, il y a des petits malins.
Ou un petit XKCD
Et si le temps le permet…
Si vous êtes bien avancés sur les TDs, voici une liste d’idées pour compléter notre site. Certaines de ces idées pourraient être particulièrement utiles pour la SAE3A.
Chargement paresseux des trajets d’un utilisateur en tant que passager
De la même manière que dans l’exercice sur recupererPassagers()
, utilisons une
jointure SQL pour trouver tous les trajets d’un utilisateur.
La nouveauté de cet exercice est que nous ne récupèrerons pas les passagers d’un
utilisateur systématiquement lors d’un appel à construireDepuisTableauSQL
(chargement hâtif). Nous récupèrerons les passagers uniquement si
l’accesseur getTrajetsCommePassager
est appelé. De plus, nous stockerons les
passagers dans un attribut afin de ne pas les récupérer plusieurs fois. Du coup,
la liste des passagers sera initialisée à null
pour indiquer que la liste n’a
pas encore été chargée.
-
Créez une fonction
recupererTrajetsCommePassager()
dansUtilisateur.php
qui retournera les trajets auxquels l’utilisateur courant est inscrit en tant que passager. Voici la signature de la fonction :/** * @return Trajet[] */ private function recupererTrajetsCommePassager() : array
- Nous allons stocker la liste des trajets comme un attribut de la classe
Utilisateur
.- Ajoutez l’attribut
/** * @var Trajet[]|null */ private ?array $trajetsCommePassager;
- Mettez à jour le constructeur pour qu’il initialise cet attribut à
null
. - Générez à l’aide de PHPStorm l’accesseur
getTrajetsCommePassager
et le modifieursetTrajetsCommePassager
. - Modifiez le code de
getTrajetsCommePassager
pour que, si$trajetsCommePassager
estnull
, alors on l’initialise à l’aide derecupererTrajetsCommePassager
.
- Ajoutez l’attribut
- Testez votre code en modifiant
lireUtilisateurs.php
pour qu’il affiche pour chaque utilisateur la liste de ses trajets en tant que passager.
Attention :
- Si on code la liste des trajets d’un utilisateur en chargement hâtif, on risque de causer une boucle infinie. En effet, le chargement d’un trajet, va impliquer le chargement de tous ses passagers (chargement hâtif), qui va charger les trajets (chargement hâtif) de tous ces passagers, qui va charger les passagers de tous les trajets de tous les passagers…
- Deux solutions existent :
- Éviter que les associations en chargement hâtif forment une boucle entre plusieurs classes comme précédemment. Pour cela, codez assez d’associations en chargement paresseux pour éviter les boucles. Ou ne codez pas que les associations dont vous aurez besoin dans l’une de vos pages Web.
- (hors programme) Mettre en place un cache au niveau de l’accès à la base de données. Cette solution est utilisée dans les solutions professionnelles de gestion des bases de données appelées ORM (cf. Hibernate au semestre 3, ou Doctrine au semestre 5). Cette stratégie est par exemple expliqué dans la documentation de Doctrine.
Désinscrire un utilisateur d’un trajet et inversement
Ajoutons une fonctionnalité : dans une future vue qui listera les trajets d’un utilisateur, nous voudrions avoir un lien ‘Désinscrire’ qui enlèvera l’utilisateur courant du trajet sélectionné.
-
Créer une méthode
public function supprimerPassager(string $passagerLogin): bool
dansTrajet.php
. Cette fonction devra désinscrire l’utilisateurpassagerLogin
du trajet courant. Laissons cette fonction retournertrue
pour le moment. -
Créez une page
supprimerPassager.php
qui reçoit unlogin
d’utilisateur et untrajet_id
via le query string de l’URL, et qui désinscrit cet utilisateur comme passager de ce trajet.Remarque : Vous aurez besoin de créer un
$trajet
pour pouvoir appeler la méthode$trajet->supprimerPassager()
. Deux possibilités :- Soit vous rajoutez une méthode
Trajet::recupererTrajetParId
similaire àUtilisateur::recupererUtilisateurParLogin
. - Soit vous créez un
$trajet
avec des fausses données, sauf l’id
qui est correct. Une manière propre de procéder est de mettre les autres attributs ànull
, ce qui implique de changer les types pour autorisernull
.
- Soit vous rajoutez une méthode
-
Ajoutez à
lireTrajets.php
de liens<a>
de désinscription pour chaque passager de chaque trajet qui renvoient sursupprimerPassager.php
en transmettant via le query string de l’URL les bonslogin
ettrajet_id
. -
Testez la désinscription.
Gestion des erreurs
Traitons plus systématiquement tous les cas particuliers. Pour l’instant, les méthodes suivantes sont correctement codées :
recupererUtilisateurs()
deUtilisateur.php
n’a pas de cas particulier,recupererUtilisateurParLogin()
deUtilisateur.php
gère un login inconnu en renvoyant l’utilisateurnull
.
Par contre, vous allez améliorer les méthodes suivantes :
ajouter()
deUtilisateur.php
ne traite pas :- le cas d’un utilisateur existant déjà en base de donnée (
SQLSTATE[23000]: Integrity constraint violation
) - le cas d’un problème de données, par exemple : chaîne de caractères trop longue (
SQLSTATE[22001]: String data, right truncation
)
- le cas d’un utilisateur existant déjà en base de donnée (
supprimerPassager()
deTrajet.php
ne traite pas le cas d’un passage inexistant.
-
Pour la méthode
ajouter()
, les cas particuliers génèrent une exception de la classePDOException
. Modifiez la déclaration de type de la méthode pour qu’elle retourne un booléen pour indiquer si la sauvegarde s’est bien passée. Modifiez la méthode pour intercepter lesPDOException
avec untry/catch
et retournerfalse
en cas de problème. -
Pour la méthode
supprimerPassager()
, utilisez la méthoderowCount()
de la classePDOStatement
pour vérifier que la requête de suppression a bien supprimé une ligne de la BDD. Modifiez la déclaration de type de la méthode pour qu’elle retourne un booléen pour indiquer si la suppression s’est bien passée. -
Lors de l’insertion dans la base de données avec
ajouter()
deTrajet
, il est possible de récupérer l’identifiant auto-incrémenté généré par la base de données et le renseigner dans l’objet courant. Pour ceci, utilisezConnexionBaseDeDonnees::getPdo()->lastInsertId()
après l’exécution de$pdoStatement->execute(...)
.
Documentation :
Quelques idées complémentaires
Voici une liste d’idées pour compléter notre site :
- Notre liste des trajets d’un utilisateur est incomplète : il manque les trajets dont il est conducteur (et non passager). La page qui liste les trajets d’un utilisateur pourrait donner les deux listes comme conducteur et comme passager.
- Pour tester votre compréhension de la transmission de données entre MySQL,
PHP et un formulaire, créer un formulaire
mettreAJourTrajet.php
qui lira l’identifiant du trajet depuis la query string, chargera le trajet depuis MySQL puis préremplira un formulaire de modification du trajet avec les valeurs du trajet actuel. Ce formulaire est proche de celui de création, à ceci près qu’il rajoute des valeurs par défaut dans les<input>
.
Créez ensuite un script de traitement du formulaire de mise à jour (proche du script de création) et vérifiez dans la base de donnée que la modification a bien marchée.