Gestion des Bases de Données
Présentation
Une base de données est un ensemble organisé de données structurées qui permet de stocker, d'accéder et de gérer efficacement des informations. Contrairement à un simple fichier ou à une liste de données, une base de données offre une structure qui permet de stocker des informations de manière logique et cohérente.
Caractéristiques principales des bases de données :
- Organisation structurée : Les données dans une base de données sont organisées de manière structurée, souvent sous forme de table, ce qui facilite la recherche, la mise à jour et l'accès aux données.
- Stockage centralisé : Les données sont stockées dans un emplacement centralisé, ce qui permet aux utilisateurs et aux applications d'accéder aux informations à partir d'un seul endroit.
- Séparation des données et de la logique : Dans une base de données bien conçue, les données sont séparées de la logique de traitement. Cela signifie que les données peuvent être modifiées sans affecter les processus ou les applications qui les utilisent.
- Partage des données : Plusieurs utilisateurs et applications peuvent partager et accéder aux données de la base de données en même temps, ce qui facilite la collaboration et l'accès aux informations à jour.
Voici quelques exemples d'applications de bases de données :
- Systèmes de gestion de la clientèle (CRM) : Les entreprises utilisent des bases de données pour stocker des informations sur leurs clients, leurs achats et leurs préférences.
- Systèmes de gestion des stocks : Les entrepôts et les magasins utilisent des bases de données pour suivre les niveaux de stock et les mouvements de produits.
- Systèmes de gestion des employés : Les entreprises gèrent les informations sur leurs employés, y compris les détails personnels, les salaires et les horaires de travail, dans des bases de données.
- Systèmes de gestion de contenu (CMS) : Les sites web et les blogs stockent leur contenu, y compris les articles, les images et les vidéos, dans des bases de données.
Les bases de données sont au coeur de nombreuses applications informatiques et de l'informatique en général. Leur utilisation permet de garantir l'intégrité, la cohérence et l'accessibilité des données, ce qui est essentiel pour de nombreuses organisations. Les bases de données sont utilisées pour prendre des décisions éclairées, automatiser des processus, gérer des informations volumineuses et maintenir des systèmes informatiques robustes.
Types de Base de Données
Il existe divers types de bases de données, notamment les bases de données relationnelles, orientées graphes, NoSQL, etc., chacune étant spécialement conçue pour répondre à des besoins particuliers en termes de stockage et de gestion de données. Par exemple, les bases de données relationnelles, telles que MySQL et PostgreSQL, sont adaptées aux données structurées, tandis que les bases de données orientées graphiques, comme Neo4j, sont idéales pour gérer des données complexes avec des relations. Les bases de données NoSQL, telles que MongoDB et Cassandra, sont privilégiées pour leur flexibilité dans la gestion de données semi-structurées ou non structurées. Chacun de ces types de bases de données joue un rôle crucial dans divers domaines d'application informatique.
Dans ce cours nous aborderons les bases de données relationnelles qui sont parfaitement adaptées aux divers besoins que nous rencontrerons.
Les bases de données relationnelles sont construites autour du concept de tables, qui sont utilisées pour organiser et stocker les données de manière structurée. Dans ce modèle, les données sont arrangées en rangées, appelées tuples, et en colonnes, désignées comme attributs, au sein des tables. Ce qui caractérise particulièrement les bases de données relationnelles, c'est la manière dont elles gèrent les relations entre les données. Elles établissent des liens grâce à l'utilisation de clés primaires et étrangères, ce qui permet de garantir l'intégrité et la cohérence des données au sein de la base de données.
Encodage et Collation d'une Base de Données
L'encodage détermine la manière dont les caractères sont stockés et transmis. L'encodage moderne recommandé est utf8mb4 car il prend en charge tout le standard Unicode, y compris les lettres accentuées, les caractères asiatiques, les symboles et les émojis. L'ancien encodage utf8 ne couvrait qu'une partie du standard et pouvait provoquer des erreurs d'affichage ou de stockage.
Choix de la collation
La collation détermine comment MySQL compare et trie les textes. Elle définit par exemple si les majuscules et minuscules doivent être distinguées, et comment les accents ou certains caractères spéciaux doivent être interprétés.
La collation utf8mb4_unicode_ci suit les règles du standard Unicode, ce qui permet à MySQL de comparer les textes de façon plus cohérente entre différentes langues. Elle tient compte des équivalences reconnues dans Unicode (ex.: la ligature œ est considérée comme identique à oe).
La collation utf8mb4_general_ci, plus ancienne, utilise des règles simplifiées qui ne tiennent pas compte de ces particularités linguistiques. Elle est légèrement plus rapide, mais la différence de performance est négligeable sur les serveurs modernes. Il est donc recommandé d'utiliser utf8mb4_unicode_ci, plus précise et mieux adaptée aux textes multilingues.
Le suffixe _ci signifie case-insensitive, ce qui indique que MySQL ne distingue pas les majuscules des minuscules lors des comparaisons.
Depuis MySQL 8, la collation par défaut est utf8mb4_0900_ai_ci. Le nombre 0900 correspond à la version 9.0.0 du standard Unicode, et la séquence _ai signifie accent-insensitive, ce qui indique que les accents sont ignorés lors des comparaisons (par exemple é est traité comme e). Cette version gère mieux les comparaisons internationales et constitue le meilleur choix pour les projets récents.
Connexion à une Base de Données
Pour interagir avec une base de données MySQL en PHP, il faut établir une connexion. PHP propose deux interfaces de programmation d'application (API : Application Programming Interface).
- MySQLi (MySQL Improved) : une extension moderne dédiée à MySQL/MariaDB. Elle fonctionne en style procédural ou orienté objet, prend en charge les requêtes préparées et permet une bonne gestion des connexions. Toutefois, elle ne fonctionne qu'avec MySQL.
- PDO (PHP Data Objects) : une interface orientée objet compatible avec plusieurs types de bases de données (MySQL, SQLite, PostgreSQL, etc.). Elle permet d'écrire un code plus flexible et plus facilement portable vers d'autres systèmes de gestion de bases de données.
En raison de sa souplesse, de sa portabilité et de sa syntaxe uniforme, nous utiliserons principalement PDO tout au long du cours.
Paramètres de connexion
Pour établir une connexion à une base de données, il faut disposer de certaines informations : le nom du serveur où se trouve la base, le nom de la base de données, un identifiant d'utilisateur et le mot de passe associé. Ces informations sont indispensables pour accéder aux données et les manipuler en toute sécurité.
<?php
$nomDuServeur = 'localhost';
$nomBDD = 'bdd_ifosup';
$nomUtilisateur = 'root';
$motDePasse = '';
// Tenter d'établir une connexion à la base de données.
try
{
// Construction du DSN (Data Source Name), c'est-à-dire la chaîne de connexion qui indique à PDO
// comment accéder à la base de données (type, serveur, nom de la base, encodage, etc.).
// Le paramètre "charset=utf8mb4" précise que la communication entre PHP et MySQL
// doit utiliser l'encodage UTF-8 complet (prise en charge des accents, caractères spéciaux et émojis).
// Cela évite les erreurs d'affichage et garantit la compatibilité avec les tables configurées en utf8mb4.
$dsn = "mysql:host=$nomDuServeur;dbname=$nomBDD;charset=utf8mb4";
// Instancier une nouvelle connexion.
$pdo = new PDO($dsn, $nomUtilisateur, $motDePasse);
// Définir le mode d'erreur sur "exception".
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
// Capturer les exceptions en cas d'erreur de connexion.
catch(PDOException $e)
{
// Afficher les potentielles erreurs rencontrées lors de la tentative de connexion à la base de données.
// Attention, ces informations pouvant être sensibles, cet affichage est réservé à la phase de développement.
echo "Erreur d'exécution de requête : {$e->getMessage()}";
}
// Libérer la connexion manuellement (facultatif, PHP la ferme automatiquement à la fin du script).
// Utile dans les scripts longs pour économiser de la mémoire et éviter de bloquer inutilement
// des connexions à la base de données (surtout si leur nombre est limité).
$pdo = null;
?>
Définir le mode de gestion des erreurs
La méthode setAttribute(), utilisée juste après la connexion, permet de définir la manière dont PDO réagit face aux erreurs d'exécution.
- PDO::ATTR_ERRMODE — définit le mode de gestion des erreurs.
- PDO::ERRMODE_EXCEPTION — active le mode dans lequel chaque erreur SQL déclenche une exception de type PDOException.
Les exceptions levées par PDO contiennent des informations utiles, comme le message d'erreur, le code SQL associé et d'autres détails exploitables. Le bloc catch permet d'utiliser ces données pour afficher un message clair, enregistrer l'erreur ou interrompre le script de manière contrôlée.
L'activation du mode PDO::ERRMODE_EXCEPTION, combinée à un bloc try/catch, rend le code plus lisible, facilite le débogage et empêche l'affichage non désiré d'informations techniques.
Créer une Base de Données
Pour créer une nouvelle base de données, la première étape consiste à se connecter au serveur, comme nous l'avons vu précédemment, mais cette fois-ci sans spécifier le nom d'une base de données existante. Ensuite, nous utilisons la méthode exec() de l'objet PDO avec en argument notre requête de création de base de données.
La méthode exec() est utilisée pour exécuter des requêtes SQL qui n'attendent pas de résultat, comme les requêtes de création, de mise à jour ou de suppression de données.
<?php
$nomDuServeur = 'localhost';
$nomUtilisateur = 'root';
$motDePasse = '';
try
{
// Construction du DSN (Data Source Name).
$dsn = "mysql:host=$nomDuServeur;charset=utf8mb4";
// Instancier une nouvelle connexion.
$pdo = new PDO($dsn, $nomUtilisateur, $motDePasse);
// Définir le mode d'erreur sur "exception".
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Exécuter la requête SQL pour créer la base de données "bdd_ifosup".
$pdo->exec('CREATE DATABASE bdd_ifosup DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;');
}
catch(PDOException $e)
{
echo "Erreur d'exécution de requête : {$e->getMessage()}";
}
?>
- CREATE DATABASE bdd_ifosup : Cette partie de la requête indique à MySQL de créer une nouvelle base de données avec le nom bdd_ifosup. Vous pouvez remplacer bdd_ifosup par le nom de votre choix pour votre base de données.
- DEFAULT CHARACTER SET utf8mb4 : Cela spécifie que la base de données utilisera utf8mb4 comme jeu de caractères par défaut. Ce jeu est la version complète d'UTF-8 dans MySQL, capable de représenter tous les caractères Unicode, y compris les emojis et les caractères multilingues complexes. Il est préférable à utf8, qui ne prend en charge que partiellement l'UTF-8.
- DEFAULT COLLATE utf8mb4_general_ci : Le COLLATE définit les règles de comparaison et de tri des chaînes de caractères dans la base. Ici, utf8mb4_general_ci indique un tri insensible à la casse (case-insensitive) et adapté aux caractères Unicode. Ainsi, "A" et "a" seront considérés comme égaux, et les recherches ou tris tiendront compte des caractères multilingues selon des règles générales.
Créer une table
Les bases de données sont organisées en utilisant des tables, qui servent à classifier les données en fonction de leur sujet. Prenons l'exemple d'une base de données pour un blog : vous y trouverez probablement une table t_utilisateur_uti pour stocker les informations sur les utilisateurs, une table t_catégorie_cat pour les catégories d'articles, une table t_article_art pour les articles eux-mêmes, et ainsi de suite. Chaque table représente un ensemble spécifique d'informations et contribue à organiser les données de manière logique et cohérente.
Pour créer une nouvelle table dans une base de données, nous utilisons la requête SQL CREATE TABLE. Par exemple, voici comment vous pourriez créer une table pour stocker des données utilisateur, telles que le pseudo et l'e-mail :
<?php
$nomDuServeur = 'localhost';
$nomBDD = 'bdd_ifosup';
$nomUtilisateur = 'root';
$motDePasse = '';
try
{
// Construction du DSN (Data Source Name).
$dsn = "mysql:host=$nomDuServeur;dbname=$nomBDD;charset=utf8mb4";
// Instancier une nouvelle connexion.
$pdo = new PDO($dsn, $nomUtilisateur, $motDePasse);
// Définir le mode d'erreur sur "exception".
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$requete = 'CREATE TABLE t_utilisateur_uti (
uti_id INT AUTO_INCREMENT PRIMARY KEY,
uti_pseudo VARCHAR(32) NOT NULL,
uti_email VARCHAR(255) UNIQUE
) ENGINE=InnoDB';
// Exécuter la requête SQL pour créer la table "t_utilisateur_uti".
$pdo->exec($requete);
}
catch(PDOException $e)
{
echo "Erreur d'exécution de requête : {$e->getMessage()}";
}
?>
- uti_id INT AUTO_INCREMENT PRIMARY KEY : Cette ligne crée une colonne appelée uti_id de type INT (entier) qui sera utilisée pour stocker l'identifiant unique de chaque utilisateur. Le mot-clé AUTO_INCREMENT indique que la valeur de cette colonne sera automatiquement incrémentée à chaque nouvelle entrée, garantissant ainsi des valeurs uniques. La clause PRIMARY KEY déclare cette colonne comme la clé primaire de la table, assurant ainsi son unicité.
- uti_pseudo VARCHAR(32) NOT NULL : Cette ligne crée une colonne appelée uti_pseudo de type VARCHAR (chaîne de caractères) avec une longueur maximale de 32 caractères. La contrainte NOT NULL signifie que chaque enregistrement doit avoir une valeur dans cette colonne, elle ne peut pas être vide.
- uti_email VARCHAR(255) UNIQUE : Cette ligne crée une colonne appelée uti_email de type VARCHAR avec une longueur maximale de 255 caractères. La contrainte UNIQUE garantit que chaque valeur dans cette colonne doit être unique parmi tous les enregistrements de la table. Cela assure qu'aucun utilisateur ne peut avoir la même adresse e-mail.
- ENGINE=InnoDB : Précise le moteur de stockage utilisé pour la table. Un moteur de stockage détermine la manière dont les données sont organisées physiquement sur le disque, et comment les opérations comme les lectures, écritures ou suppressions sont gérées. Le moteur InnoDB est aujourd'hui le plus utilisé avec MySQL, car il prend en charge les transactions, les clés étrangères (relations entre tables) et assure une meilleure intégrité des données.
Types de données
MySQL offre une variété de types de données pour chaque colonne, en fonction du type de données que vous souhaitez stocker. Voici quelques-uns des types de données les plus couramment utilisés :
- INT : Utilisé pour les nombres entiers (de -2,147,483,648 à 2,147,483,647).
uti_age INT - DECIMAL(M,D) : Définit une colonne pour stocker des nombres avec une précision fixe, idéale pour les montants financiers ou des valeurs décimales précises.
- M : Le nombre maximal de chiffres à stocker, qu'ils soient avant ou après la virgule décimale.
- D : Le nombre de chiffres après la virgule décimale.
-- Définit une colonne pour stocker un prix avec jusqu'à 10 chiffres au total, -- dont 2 après la virgule, permettant de représenter des valeurs comme 12345678.90. art_prix DECIMAL(10,2) - CHAR(L) : Utilisé pour stocker des chaînes de caractères de longueur fixe. Si la chaîne insérée est plus courte que la longueur spécifiée, elle sera automatiquement complétée avec des espaces à droite pour atteindre la longueur exacte.
- L : Le nombre exact de caractères à stocker.
art_code CHAR(10) - VARCHAR(L) : Pour les chaînes de caractères de longueur variable.
- L : Le nombre maximal de caractères pouvant être stockés.
art_nom VARCHAR(255) - TEXT : Conçu pour stocker de grandes quantités de texte sans imposer de limite fixe au nombre de caractères dans la déclaration.
description TEXT - DATE : Pour stocker des dates au format YYYY-MM-DD.
uti_date_creation DATE - BOOLEAN : Utilisé pour représenter des valeurs booléennes, où 0 correspond à faux et 1 à vrai.
art_en_stock BOOLEAN - BINARY(L) : Pour stocker des données binaires de longueur fixe, comme des jetons, des clés cryptographiques ou des hachages. Idéal pour économiser de l'espace et optimiser les comparaisons de données brutes.
- L : Le nombre exact d'octets à stocker.
uti_jeton BINARY(16)
- L : Le nombre maximal d'octets pouvant être stockés.
uti_carte_crypt VARBINARY(255)
Pour en apprendre plus sur les types de données, rendez-vous sur la documentation officielle de MySQL.
Contraintes et attributs des colonnes
En plus des types de données, vous pouvez ajouter des attributs ou des contraintes à chaque colonne pour définir des comportements spécifiques :
- PRIMARY KEY : Identifie de manière unique chaque enregistrement dans la table.
art_id INT PRIMARY KEY - AUTO_INCREMENT : MySQL incrémente automatiquement la valeur de la colonne pour chaque nouvel enregistrement.
art_id INT AUTO_INCREMENT PRIMARY KEY - UNIQUE : Chaque valeur dans la colonne doit être unique.
uti_email VARCHAR(255) UNIQUE NOT NULL - NOT NULL : Signifie que chaque entrée doit contenir une valeur pour cette colonne.
art_nom VARCHAR(100) NOT NULL - DEFAULT : Définit une valeur par défaut pour une colonne si aucune valeur n'est fournie lors de l'insertion d'un enregistrement.
art_stock INT DEFAULT 10 - UNSIGNED : Utilisé pour les données de type numérique, indiquant que seules les valeurs positives sont autorisées.
art_montant DECIMAL(10,2) UNSIGNED - FOREIGN KEY : Utilisé pour créer des relations entre les tables en liant une colonne à une clé primaire d'une autre table. Cette contrainte garantit l'intégrité référentielle.
CONSTRAINT fk_t_commandes_com_t_utilisateurs_uti FOREIGN KEY (com_uti_id) REFERENCES t_utilisateurs_uti(uti_id) - ON DELETE et ON UPDATE : Utilisé en conjonction avec les clés étrangères pour définir les actions à entreprendre lorsque des enregistrements parents sont supprimés ou mis à jour.
CONSTRAINT fk_t_commandes_com_t_utilisateurs_uti FOREIGN KEY (com_uti_id) REFERENCES t_utilisateurs_uti(uti_id) ON DELETE CASCADE ON UPDATE CASCADE - SET NULL : Définit les valeurs des colonnes enfants à NULL lorsque les enregistrements parents sont supprimés ou mis à jour.
CONSTRAINT fk_t_commandes_com_t_utilisateurs_uti FOREIGN KEY (com_uti_id) REFERENCES t_utilisateurs_uti(uti_id) ON DELETE SET NULL - ...
Introduction au CRUD
Le terme CRUD désigne les quatre opérations fondamentales que l'on peut effectuer sur des données dans une base relationnelle : Create (Créer), Read (Lire), Update (Mettre à jour) et Delete (Supprimer). Ces opérations sont au cœur de toute application dynamique, qu'il s'agisse de gérer des utilisateurs, des produits ou des articles.
Avant de réaliser la moindre opération CRUD, il faut établir une connexion à la base de données. Pour cela, nous utilisons la classe PDO, qui permet d'interagir avec MySQL de manière sécurisée et souple.
Plutôt que de dupliquer les paramètres de connexion et les traitements d'erreurs dans chaque script, nous regroupons l'ensemble de la logique générique liée à la base de données dans des fichiers séparés. Cela inclut la fonction de connexion centralisée ainsi qu'un système de gestion des erreurs. Cette structure rend le code plus clair, réutilisable et facile à maintenir, tout en préparant le projet à évoluer vers des architectures plus complexes où les responsabilités sont bien séparées (configuration, accès aux données, journalisation, etc.) :
📁 monProjet/
├── 📁 config/
│ └── 📄 config.php
├── 📁 core/
│ ├── 📄 gestionBdd.php
│ └── 📄 gestionErreur.php
├── 📁 logs/
│ └── 📄 erreurs.php
└── 📄 index.php
Configuration de la base de données
Nous créons un fichier /config/config.php qui regroupe plusieurs paramètres de configuration, notamment ceux liés à la connexion à la base de données. Dans un projet plus important, il serait judicieux de répartir ces paramètres dans plusieurs fichiers spécialisés. Ici, pour simplifier, nous rassemblons les éléments dans un seul fichier.
<?php
// Fichier : /config/config.php.
// Active le mode développement.
// Ce mode permet notamment d'afficher les erreurs à l'écran pour faciliter le débogage.
// En production, cette constante devra être désactivée pour éviter de révéler des informations sensibles.
define('DEV_MODE', true);
// Retourne les paramètres nécessaires à la connexion à la base de données.
// Cette fonction centralise la configuration de la connexion, ce qui permet
// d'éviter de dupliquer ces informations dans tout le projet.
function obtenirConfigBdd(): array
{
return [
'serveur' => 'localhost',
'bdd' => 'bdd_ifosup',
'utilisateur' => 'root',
'mdp' => ''
];
}
?>
Fichier central de gestion de la base
Nous créons un fichier /core/gestionBdd.php qui contient les fonctions génériques liées à la base de données, comme celle permettant de s'y connecter :
<?php
// Fichier : /core/gestionBdd.php.
// Importer les dépendances.
require_once dirname(__DIR__) . DIRECTORY_SEPARATOR 'config' . DIRECTORY_SEPARATOR . 'config.php';
function obtenirConnexionBdd(): PDO
{
// Récupérer la configuration permettant d'établir une connexion à la base de données.
$config = obtenirConfigBdd();
// Construire le DSN (Data Source Name).
$dsn = "mysql:host={$config['serveur']};dbname={$config['bdd']};charset=utf8mb4";
// Établir connexion à la base de données.
$pdo = new PDO($dsn, $config['utilisateur'], $config['mdp']);
// Activer les Exceptions en cas d'erreur.
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $pdo;
}
?>
Fichier de gestion des erreurs
Nous créons également un fichier /core/gestionErreurs.php qui contient une fonction dédiée au traitement des exceptions :
<?php
// Fichier : /core/gestionErreurs.php.
// Charger le fichier de configuration globale ("config.php").
// Ce fichier contient, entre autres, la constante "DEV_MODE" utilisée ici pour déterminer
// si les erreurs doivent être affichées à l'écran ou non.
//
// Même si ce fichier est déjà inclus ailleurs dans l'application (comme dans "gestionBdd.php"),
// on préfère l'inclure à nouveau ici pour être sûr que les informations soient disponibles,
// notamment si cette fonction est utilisée de manière autonome.
//
// La fonction "require_once" permet de ne charger le fichier qu'une seule fois, même si
// plusieurs scripts essaient de l'inclure : cela évite les doublons ou erreurs de redéfinition.
require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'config.php';
function gererExceptions(Exception $e): void
{
if (defined('DEV_MODE') && DEV_MODE === true)
{
echo 'Une erreur est survenue : ' . $e->getMessage();
}
else
{
// Définir le chemin complet vers le fichier de log des erreurs.
$cheminLog = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'logs' . DIRECTORY_SEPARATOR . 'erreurs.log';
// Construire un message d'erreur horodaté, contenant la date et l'heure suivies du message de l'exception.
$message = date('[Y-m-d H:i:s]') . $e->getMessage();
// Enregistrer le message dans le fichier de log.
// Le paramètre "3" indique à error_log() d'écrire dans un fichier personnalisé.
error_log($message, 3, $cheminLog);
}
}
?>
Utilisation dans les scripts
Dans vos scripts, vous pouvez désormais utiliser les fonctions centralisées pour interagir avec la base de données en toute simplicité :
<?php
// Fichier : index.php
// Importer les dépendances.
require_once __DIR__ . DIRECTORY_SEPARATOR . 'core' . DIRECTORY_SEPARATOR . 'gestionBdd.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'core' . DIRECTORY_SEPARATOR . 'gestionErreurs.php';
try
{
// Obtenir la connexion à la BDD.
$pdo = obtenirConnexionBdd();
// Ici, on peut exécuter des requêtes CRUD...
}
catch (PDOException $e)
{
gererExceptions($e);
}
finally
{
// Libérer la connexion une fois la/les requêtes réalisée(s).
$pdo = null;
}
?>
Exercices - Part 01
Exo-gestion-des-bases-de-donnees-01
- Objectif : Concevoir une base de données et une table pour gérer les utilisateurs.
- Instructions :
- Vous disposez de plusieurs méthodes pour créer la base de données et la table des utilisateurs :
- Utilisation de l'interface MySQL dans le terminal système.
- Utilisation de phpMyAdmin (généralement disponible à l'adresse http://localhost/phpmyadmin) :
- Exécuter vos requêtes SQL dans l'onglet SQL.
- Utiliser l'interface graphique.
- Utilisation d'instructions PHP, en vous référant aux exemples présentés dans les sections précédentes.
- Créez une base de données nommée bdd_php_exo_bdd avec un jeu de caractères utf8mb4 pour garantir une compatibilité avec une large gamme de caractères utilisés dans différentes langues. Configurez également une collation utf8mb4_general_ci. Le suffixe ci signifie case insensitive, ce qui permet de ne pas différencier les majuscules des minuscules lors des recherches et des comparaisons (par exemple, "A" sera traité comme équivalent à "a").
- Créez une table nommée t_utilisateur_uti dans la base de données bdd_php_exo_bdd. La table doit contenir les colonnes suivantes :
- uti_id : un identifiant unique auto-incrémenté qui sert de clé primaire.
- uti_nom : une colonne pour le nom de l'utilisateur, qui doit être facultative.
- uti_prenom : une colonne pour le prénom de l'utilisateur, également facultative.
- uti_email : une colonne pour l'adresse email de l'utilisateur, qui doit être obligatoire et unique.
- Configurez la table pour utiliser le moteur de stockage InnoDB.
- Initialisez la table t_utilisateur_uti en insérant les 3 enregistrements suivants (format : nom, prénom, email) :
- Nom : JC, Prénom : JC, Email : jc.jc@fritkot.be
- Nom : Focan, Prénom : Claudy, Email : claudy.focan@gmail.com
- Nom : Burton, Prénom : Jack, Email : jb@carpenter.com
- Vérifiez que les créations ont été configurées conformément aux attentes en utilisant les méthodes présentées à la première étape.
- Vous disposez de plusieurs méthodes pour créer la base de données et la table des utilisateurs :
Exo - Gestion des bases de données 02
- Objectif : Concevoir un gestionnaire de base de données générique et modulaire. L'objectif est d'encapsuler la logique de connexion et de configuration dans des fichiers réutilisables, tout en garantissant une gestion claire et sécurisée des erreurs.
- Instructions :
- Créez l'arborescence suivante en respectant la structure des dossiers et fichiers :
Le dossier config contiendra les paramètres globaux de configuration, comme le nom d'utilisateur ou le mot de passe de connexion à la base de données. Le dossier core servira à regrouper des fonctions génériques réutilisables dans plusieurs projets, telles que celles liées à la base de données, aux formulaires ou aux envois d'emails.📁 exo-gestion-des-bases-de-donnees-02/ ├── 📁 config/ │ └── 📄 config.php ├── 📁 core/ │ ├── 📄 gestionBdd.php │ └── 📄 gestionErreurs.php └── 📄 index.php - Dans config/config.php :
- Définissez une constante DEV_MODE à true (pour le moment), afin d'activer l'affichage des erreurs en mode développement.
- Implémentez la fonction obtenirConfigBdd() qui retourne un tableau associatif contenant les paramètres suivants :
- serveur (Adresse du serveur de BDD) : Par défaut, pour un environnement local, cette valeur est localhost.
- bdd : Le nom de la base de données avec laquelle vous souhaitez communiquer.
- utilisateur : Par défaut, pour un environnement local, cette valeur est root.
- mdp : Par défaut, pour un environnement local, il n'y a pas de mot de passe (une chaîne de caractères vide). Cependant, si un mot de passe a été défini lors de l'installation de MySQL, utilisez celui-ci.
- Dans core/gestionBdd.php :
- Importez le fichier config.php pour avoir accès aux paramètres de connexion à la base de données.
- Implémentez une fonction obtenirConnexionBdd(string $nomBDD) qui :
- Utilise obtenirConfigBdd() pour récupérer les paramètres de connexion
- Construit dynamiquement un DSN à partir du nom de base de données passé en argument
- Crée une connexion PDO et configure le mode de gestion des erreurs avec PDO::ATTR_ERRMODE afin que les erreurs soient levées sous forme d'exceptions.
- Retourne un objet PDO prêt à exécuter des requêtes
- Dans core/gestionErreurs.php :
- Importez le fichier config.php afin de pouvoir exploiter la constante DEV_MODE et adapter le comportement de la fonction selon que l'application est en mode développement ou en mode production.
- Ce fichier doit contenir une fonction gererExceptions() prenant en paramètre une exception Exception.
- En mode développement, la fonction devra afficher le message d'erreur à l'écran.
- En mode production, elle ne devra rien afficher à l'écran, mais enregistrer l'erreur dans un fichier de log (logs/erreurs.log).
- Pour écrire dans un fichier de log, utilisez la fonction native error_log() avec le paramètre
3.
- Dans index.php :
- Importez les dépendances gestionBdd.php et gestionErreurs.php.
- Tentez d'établir une connexion à la base de données à l'aide de la fonction obtenirConnexionBdd().
- Encapsulez l'appel dans un bloc try/catch :
- Si la connexion réussit, affichez un message confirmant le succès de la connexion.
- Si une erreur survient, appelez la fonction de gestion des erreurs gererExceptions().
- Ajoutez un bloc finally pour libérer la connexion quoi qu'il arrive.
- Testez le code :
- Vérifiez que le message de confirmation s'affiche correctement lorsque la connexion réussit.
- En mode développement, vérifiez qu'un message d'erreur s'affiche depuis le bloc catch si les informations de connexion sont incorrectes.
- En mode production, assurez-vous qu'aucun message d'erreur ne s'affiche à l'écran si les informations de connexion sont incorrectes.
- Créez l'arborescence suivante en respectant la structure des dossiers et fichiers :
Récupérer des éléments d'une table
La lecture d'enregistrements consiste à extraire des données d'une table. Utilisez une requête SQL SELECT... pour récupérer des données de la table :
<?php
try
{
// Instancier la connexion à la base de données.
$pdo = obtenirConnexionBdd();
// Cette requête interroge la table "t_utilisateur_uti" afin de retourner tous les utilisateurs.
$requete = 'SELECT * FROM t_utilisateur_uti';
// La méthode "query()" envoie la requête SQL au serveur et l'exécute immédiatement.
// Le serveur prépare alors le jeu de résultats et renvoie un pointeur vers ces données,
// sans encore les transférer en mémoire PHP.
$stmt = $pdo->query($requete);
// La méthode "fetchAll()" parcourt ce pointeur et récupère toutes les lignes du jeu de résultats en mémoire PHP.
// Le mode "PDO::FETCH_ASSOC" indique que chaque ligne est retournée sous forme de tableau associatif
// où chaque clé correspond au nom d'une colonne de la table (nomColonne => valeur).
$utilisateurs = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Vérifier si des utilisateurs ont été trouvés.
if ($utilisateurs)
{
// Afficher la liste des utilisateurs dans un format HTML.
?><ul><?php
foreach ($utilisateurs as $utilisateur)
{
// Échapper les valeurs pour éviter les attaques XSS.
// Rappel :
// - ENT_QUOTES :
// Convertit les guillemets simples (' ') et doubles (" ") en entités HTML.
// Par défaut, PHP utilise ENT_COMPAT, qui ne convertit que les guillemets doubles.
// - ENT_SUBSTITUTE :
// Remplace tout caractère invalide pour l'encodage (ex. UTF-8 mal formé)
// par le symbole de remplacement � au lieu de provoquer une erreur.
$pseudo = htmlspecialchars($utilisateur['uti_pseudo'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$email = htmlspecialchars($utilisateur['uti_email'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
?>
<li>Pseudo : <?=$pseudo?>, E-mail : <?=$email?></li>
<?php
}
?></ul><?php
}
}
catch(PDOException $e)
{
gererExceptions($e);
}
finally
{
// Libérer la connexion, que le bloc try se soit exécuté entièrement ou qu'une exception ait été capturée.
$pdo = null;
}
?>
- query() : Permet d'exécuter une requête SQL qui retourne un jeu de résultats, comme une requête SELECT. Par défaut, cette méthode retourne false en cas d'erreur SQL. Toutefois, dans ce projet, la fonction obtenirConnexionBdd() configure PDO avec l'option PDO::ATTR_ERRMODE sur PDO::ERRMODE_EXCEPTION, ce qui permet de déclencher automatiquement une exception PDOException en cas d'erreur. Il devient donc inutile de tester manuellement la réussite avec if ($stmt).
- fetchAll() : Permet de récupérer l'ensemble des résultats d'une requête SQL sous forme de tableau. Chaque élément de ce tableau correspond à une ligne de la table (ici, à un utilisateur). Si aucun enregistrement n'est trouvé, la méthode retourne un tableau vide. Le paramètre PDO::FETCH_ASSOC précise que chaque ligne sera récupérée sous forme d'un tableau associatif, avec les noms des colonnes comme clés. Par défaut, c'est le paramètre
- PDO::FETCH_BOTH qui est configuré. Ce mode retourne chaque ligne sous forme d'un tableau mixte, contenant à la fois les clés numériques (index des colonnes) et associatives (noms des colonnes). Chaque valeur est donc accessible deux fois, ce qui peut alourdir la structure inutilement.
- htmlspecialchars() : Cette fonction permet d'échapper les caractères spéciaux d'une chaîne avant son affichage dans une page HTML, afin d'éviter les injections HTML ou les attaques XSS.
- ENT_QUOTES : échappe à la fois les guillemets simples et doubles.
- ENT_SUBSTITUTE : remplace les caractères invalides pour l'encodage spécifié par le symbole �, ce qui évite les erreurs d'affichage.
- 'UTF-8' : indique l'encodage attendu, ce qui garantit une compatibilité avec les contenus multilingues.
Lorsqu'une requête SQL est censée retourner une seule ligne, il est préférable d'utiliser la méthode fetch() plutôt que fetchAll(). Cela rend le code plus clair et évite de parcourir inutilement un tableau.
<?php
try
{
// Instancier la connexion à la base de données.
$pdo = obtenirConnexionBdd();
// Cette requête sélectionne tous les champs de l'utilisateur dont l'identifiant est 1.
// Comme "uti_id" est une clé primaire (donc unique et indexée), il ne peut y avoir qu'une seule ligne correspondante.
// Le moteur MySQL utilise l'index pour accéder directement à l'enregistrement sans parcourir toute la table.
// Il n'est donc pas nécessaire d'ajouter "LIMIT 1", car la base de données sait qu'il n'y aura au maximum qu'un seul résultat.
$requete = 'SELECT * FROM t_utilisateur_uti WHERE uti_id = 1';
// Exécuter la requête.
$stmt = $pdo->query($requete);
// Récupérer les informations de l'utilisateur.
$utilisateur = $stmt->fetch(PDO::FETCH_ASSOC);
if ($utilisateur)
{
// Échapper les valeurs pour éviter les attaques XSS.
$pseudo = htmlspecialchars($utilisateur['uti_pseudo'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$email = htmlspecialchars($utilisateur['uti_email'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
?>
<p>Pseudo : <?=$pseudo?>, E-mail : <?=$email?></p>
<?php
}
}
catch (PDOException $e)
{
// Gérer l'exception via une fonction dédiée.
gererExceptions($e);
}
finally
{
// Libérer la connexion, que le bloc try se soit exécuté entièrement ou qu'une exception ait été capturée.
$pdo = null;
}
?>
Bien que fetch() soit particulièrement adapté aux requêtes ne retournant qu'une seule ligne, il peut également être utilisé dans une boucle pour parcourir les lignes d'un résultat une par une. Cette approche est souvent plus économe en mémoire que fetchAll(), surtout lorsque la requête retourne un grand nombre d'enregistrements.
<?php
try
{
// Instancier la connexion à la base de données.
$pdo = obtenirConnexionBdd();
// Cette requête interroge la table "t_utilisateur_uti" afin de retourner tous les utilisateurs.
$requete = 'SELECT * FROM t_utilisateur_uti';
// Exécuter la requête.
$stmt = $pdo->query($requete);
// Parcourir les utilisateurs issus de l'exécution de la requête et afficher leurs informations dans une liste :
?><ul><?php
while ($utilisateur = $stmt->fetch(PDO::FETCH_ASSOC))
{
// Échapper les valeurs pour éviter les attaques XSS.
$pseudo = htmlspecialchars($utilisateur['uti_pseudo'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$email = htmlspecialchars($utilisateur['uti_email'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
?>
<li>Pseudo : <?=$pseudo?>, E-mail : <?=$email?></li>
<?php
}
?></ul><?php
}
catch(PDOException $e)
{
gererExceptions($e);
}
finally
{
// Libérer la connexion, que le bloc try se soit exécuté entièrement ou qu'une exception ait été capturée.
$pdo = null;
}
?>
Les requêtes préparées
Dans diverses requêtes, vous devrez fréquemment inclure des valeurs dynamiques, souvent issues de formulaires remplis par les utilisateurs de votre application :
<?php
// Les données provenant d'un formulaire "mot de passe oublié".
print_r($_POST);
/*
Affiche :
Array
(
[email] => "email@bidon.com'; DROP TABLE t_utilisateur_uti; -- "
)
*/
try
{
// Instancier la connexion à la base de données.
$pdo = obtenirConnexionBdd();
$email = $_POST['email'];
// NE FAITES JAMAIS CECI !!!
// Cela expose votre application aux attaques par injection SQL.
// Un utilisateur pourrait manipuler la requête en injectant du code
// malveillant via le champ "email" du formulaire.
$requete = "
SELECT *
FROM t_utilisateur_uti
WHERE uti_pseudo = '$email'
";
// Exécuter la requête.
$stmt = $pdo->query($requete);
// Récupérer l'utilisateur issu de la requête.
$utilisateur = $stmt->fetch(PDO::FETCH_ASSOC);
// Vérifier si un utilisateur correspond à l'email saisi.
// Cela permet de s'assurer qu'on ne tente pas d'envoyer un e-mail à une adresse ne figurant pas dans la BDD.
if ($utilisateur)
{
// À ce stade, un utilisateur a bien été trouvé en base.
// Il est désormais possible de déclencher l'envoi d'un courriel
// pour lui permettre de réinitialiser son mot de passe...
}
}
catch(PDOException $e)
{
gererExceptions($e);
}
finally
{
// Libérer la connexion, que le bloc try se soit exécuté entièrement ou qu'une exception ait été capturée.
$pdo = null;
}
?>
Introduire directement des variables dans une requête SQL lorsque celles-ci proviennent d'un utilisateur est une pratique dangereuse, car cela ouvre la porte aux attaques par injection SQL. Un utilisateur malveillant pourrait, par exemple, entrer dans le champ email une valeur comme email@bidon.com'; DROP TABLE t_utilisateur_uti; -- . Cette chaîne contient une instruction SQL supplémentaire, suivie du double tiret -- qui permet de commenter le reste de la ligne.
La requête initialement prévue :
SELECT * FROM t_utilisateur_uti WHERE uti_email = '$email';
Serait transformée ainsi avec l'injection :
SELECT * FROM t_utilisateur_uti WHERE uti_email = 'email@bidon.com'; DROP TABLE t_utilisateur_uti; -- ';
- La première partie SELECT * FROM t_utilisateur_uti WHERE uti_email = 'email@bidon.com'; ne renverra aucun résultat, car bien que l'adresse soit valide sur le plan syntaxique, il est peu probable qu'un tel utilisateur existe réellement dans la base de données.
- En revanche, la seconde instruction DROP TABLE t_utilisateur_uti; sera bel et bien exécutée, entraînant la suppression complète de la table t_utilisateur_uti.
- Enfin, le double tiret -- est utilisé par les attaquants pour commenter la suite de la requête SQL originale ';. Il ne sert pas à ignorer toute la requête, mais uniquement ce qui se trouve après l'injection. Lors d'une attaque, seule la partie contrôlée par l'utilisateur est injectée dans la requête. Si celle-ci est suivie d'une partie imposée par le code (comme une apostrophe de fermeture ou une autre condition), cela peut provoquer une erreur de syntaxe. En ajoutant --, le pirate désactive le reste de la ligne, évitant ainsi l'erreur et permettant à son code malveillant de s'exécuter correctement.
Afin d'éviter de telles situations, il est recommandé d'utiliser des requêtes préparées :
<?php
// Les données provenant d'un formulaire "mot de passe oublié".
print_r($_POST);
/*
Affiche :
Array
(
[email] => ' OR uti_pseudo = 'pseudoAdmin'; --
)
*/
try
{
// Instancier la connexion à la base de données.
$pdo = obtenirConnexionBdd();
// Un marqueur, nommé selon notre convenance et préfixé par deux points ":", est utilisé pour représenter la valeur dynamique.
$email = $_POST['email'];
// Requête SQL sécurisée avec des paramètres nommés (:pseudo et :motDePasse)
// pour éviter les attaques par injection SQL.
$requete = '
SELECT *
FROM t_utilisateur_uti
WHERE uti_email = :email
';
// Préparation de la requête SQL.
$stmt = $pdo->prepare($requete);
// Liaison de la variable "$email" à son marqueur ":email".
// Le premier paramètre est correspond au marqueur présent dans la requête.
// Le seconde paramètre est la valeur liée à ce marqueur.
// Le troisième paramètre est facultatif et permet de préciser le type attendu de la valeur liée :
// PDO::PARAM_INT : entier.
// PDO::PARAM_STR : une chaîne de caractères.
// PDO::PARAM_BOOL : booléen.
// PDO::PARAM_NULL : NULL.
$stmt->bindValue(':email', $email, PDO::PARAM_STR);
// Exécution de la requête.
// C'est à ce moment-ci que la valeur des variables liées sont récupérée et interprétées.
$stmt->execute();
// Récupérer l'utilisateur issu de la requête.
$utilisateur = $stmt->fetch(PDO::FETCH_ASSOC);
// Vérifier si un utilisateur correspond à l'email saisi.
// Cela permet de s'assurer qu'on ne tente pas d'envoyer un e-mail à une adresse ne figurant pas dans la BDD.
if ($utilisateur)
{
// À ce stade, un utilisateur a bien été trouvé en base.
// Il est désormais possible de déclencher l'envoi d'un courriel
// pour lui permettre de réinitialiser son mot de passe...
}
}
catch(PDOException $e)
{
gererExceptions($e);
}
finally
{
// Libérer la connexion, que le bloc try se soit exécuté entièrement ou qu'une exception ait été capturée.
$pdo = null;
}
?>
L'utilisation des requêtes préparées en conjonction avec des marqueurs nommés permet de prévenir efficacement les injections SQL. Voici le fonctionnement en trois étapes :
- Requête préparée : Lorsqu'on utilise la méthode prepare(), la structure de la requête SQL (avec ses marqueurs, comme :email) est envoyée séparément des données. À ce stade, aucun contenu utilisateur n'est encore intégré.
- Liaison des paramètres : La méthode bindValue() permet de lier chaque valeur réelle (par exemple, $email) à son marqueur correspondant. Ces valeurs sont transmises séparément et interprétées comme des données, non comme du code SQL.
- Exécution sécurisée : Lors de l'appel à execute(), la base de données combine la requête et les valeurs tout en les considérant comme du contenu à insérer, ce qui neutralise toute tentative d'injection SQL.
Notez que l'ajout de valeur dynamique placée directement dans la requête SQL n'était pas la seule erreur dans le code.
Insérer un nouvel élément dans une table
La création d'un enregistrement consiste à ajouter de nouvelles données à une table. Par exemple, un utilisateur peut publier un commentaire via un formulaire. Pour cela, on utilise une requête SQL INSERT INTO... :
<?php
// Données simulées comme si elles provenaient d'un formulaire de commentaire.
print_r($_POST);
/*
Affiche :
Array
(
[auteur] => Claudy Focan
[contenu] => J'adore ce site, continuez comme ça !
)
*/
try
{
$pdo = obtenirConnexionBdd();
$requete = '
INSERT INTO t_commentaire_com (
com_auteur,
com_contenu
)
VALUES (
:auteur,
:contenu
)
';
// Préparation de la requête SQL.
$stmt = $pdo->prepare($requete);
// Lier chaque marqueur à sa valeur fixe transmise via le formulaire.
$stmt->bindValue(':auteur', $_POST['auteur'], PDO::PARAM_STR);
$stmt->bindValue(':contenu', $_POST['contenu'], PDO::PARAM_STR);
$stmt->execute();
}
catch(PDOException $e)
{
gererExceptions($e);
}
finally
{
// Libérer la connexion, que le bloc try se soit exécuté entièrement ou qu'une exception ait été capturée.
$pdo = null;
}
?>
Dans certaines situations, comme lors de l'importation de plusieurs commandes ou de l'ajout de plusieurs lignes dans une table (par exemple depuis un fichier CSV ou une API), on souhaite exécuter plusieurs fois une même requête avec des valeurs différentes.
Pour cela, on peut préparer la requête une seule fois, puis la réutiliser dans une boucle. L'utilisation de bindParam() permet de lier des variables par référence. Ainsi, à chaque itération, on met à jour le contenu des variables, et ces nouvelles valeurs sont automatiquement prises en compte lors de l'exécution. Cela évite de relier à nouveau les paramètres à chaque passage et peut légèrement améliorer les performances tout en allégeant le code.
<?php
// Exemple d'importation de commandes reçues via une API ou un fichier CSV.
$commandes = [
['client_id' => 1, 'produit_id' => 101, 'quantite' => 2],
['client_id' => 2, 'produit_id' => 104, 'quantite' => 1],
['client_id' => 1, 'produit_id' => 105, 'quantite' => 3]
];
// Variables à lier par référence
$clientId = null;
$produitId = null;
$quantite = null;
try
{
$pdo = obtenirConnexionBdd();
// Préparation de la requête d'insertion.
$requete = '
INSERT INTO t_commande_com (
com_client_id,
com_produit_id,
com_quantite
) VALUES (
:client_id,
:produit_id,
:quantite
)
';
// Préparation de la requête SQL.
$stmt = $pdo->prepare($requete);
// Liaison des variables aux marqueurs (par référence).
$stmt->bindParam(':client_id', $clientId, PDO::PARAM_INT);
$stmt->bindParam(':produit_id', $produitId, PDO::PARAM_INT);
$stmt->bindParam(':quantite', $quantite, PDO::PARAM_INT);
// À chaque itération, les variables sont mises à jour avec les valeurs de la commande courante.
// Étant donné qu'elles ont été liées par référence via bindParam(), leurs nouvelles valeurs
// seront automatiquement utilisées lors de l'exécution de la requête.
foreach ($commandes as $commande)
{
$clientId = $commande['client_id'];
$produitId = $commande['produit_id'];
$quantite = $commande['quantite'];
$stmt->execute();
}
}
catch (PDOException $e)
{
gererExceptions($e);
}
finally
{
// Libérer la connexion, que le bloc try se soit exécuté entièrement ou qu'une exception ait été capturée.
$pdo = null;
}
?>
Mise à jour d'un élément d'une table
La mise à jour d'enregistrements permet de modifier des données existantes dans une table. Utilisez une requête SQL UPDATE... pour mettre à jour des données :
<?php
// Les données provenant d'un formulaire permettant de modifier son pseudo.
print_r($_POST);
/*
Affiche :
Array
(
[utilisateur_id] => 2
[utilisateur_nouveau_pseudo] => JC
)
*/
try
{
$pdo = obtenirConnexionBdd();
// La requête permettant d'actualiser le pseudo d'un utilisateur à partir de son ID.
$requete = '
UPDATE t_utilisateur_uti
SET uti_pseudo = :nouveauPseudo
WHERE uti_id = :idUtilisateur
';
// Préparer la requête SQL.
$stmt = $pdo->prepare($requete);
// Lier les variables aux marqueurs.
$stmt->bindValue(':nouveauPseudo', $_POST['utilisateur_nouveau_pseudo'], PDO::PARAM_STR);
$stmt->bindValue(':idUtilisateur', $_POST['utilisateur_id'], PDO::PARAM_INT);
// Exécuter la requête.
$stmt->execute();
}
catch(PDOException $e)
{
gererExceptions($e);
}
finally
{
// Libérer la connexion, que le bloc try se soit exécuté entièrement ou qu'une exception ait été capturée.
$pdo = null;
}
?>
Supprimer un élément d'une table
La suppression d'enregistrements permet de supprimer des données d'une table. Utilisez une requête SQL DELETE... pour supprimer des données :
<?php
// Les données provenant d'un formulaire permettant de supprimer un utilisateur.
print_r($_POST);
/*
Affiche :
Array
(
[utilisateur_id] => 2
)
*/
try
{
$pdo = obtenirConnexionBdd();
// La requête permettant de supprimer un utilisateur à partir de son ID.
$requete = '
DELETE FROM t_utilisateur_uti
WHERE uti_id = :idUtilisateur
';
// Préparer la requête SQL.
$stmt = $pdo->prepare($requete);
// Lier la valeur de "$_POST['utilisateur_id']" à son marqueur ":idUtilisateur".
$stmt->bindValue(':idUtilisateur', $_POST['utilisateur_id'], PDO::PARAM_INT);
// Exécuter la requête.
$stmt->execute();
}
catch(PDOException $e)
{
gererExceptions($e);
}
finally
{
// Libérer la connexion, que le bloc try se soit exécuté entièrement ou qu'une exception ait été capturée.
$pdo = null;
}
?>
Exercices - Part 02
Exo-gestion-des-bases-de-donnees-03
- Objectif : Développer un modèle contenant les fonctions nécessaires pour effectuer des requêtes vers la table t_utilisateurs_uti, en commençant par une fonction permettant de sélectionner un utilisateur à partir de son email.
- Instructions :
- Dupliquez le projet Exo-gestion-des-bases-de-donnees-02 et renommez-le en Exo-gestion-des-bases-de-donnees-03.
- Ajoutez un dossier models à la racine du projet ainsi qu'un fichier nommé utilisateursModel.php. Voici à quoi devrait ressembler la structure des fichiers de votre projet :
models : Ce dossier contiendra les fonctions permettant d'interagir avec une table spécifique de la base de données.📁 exo-gestion-des-bases-de-donnees-03/ ├── 📁 config/ │ └── 📄 config.php ├── 📁 core/ │ ├── 📄 gestionBdd.php │ └── 📄 gestionErreurs.php ├── 📁 models/ │ └── 📄 utilisateursModel.php └── 📄 index.php - Le fichier utilisateursModel.php sera utilisé pour centraliser les fonctions de requêtes liées à la table t_utilisateurs_uti. Cette fonction devra suivre les étapes suivantes :
- Incluez le fichier gestionBdd.php pour accéder aux fonctions génériques de gestion de la base de données.
- Créez une fonction selectionnerUtilisateurParSonEmail() pour sélectionner un utilisateur par son email :
- Connectez-vous à la base de données en utilisant la fonction obtenirobtenirConnexionBdd() définie dans le fichier gestionBdd.php.
- Effectuez une requête SQL simple pour sélectionner un utilisateur en fonction de son email passé en paramètre. Pour commencer, utilisez une requête non préparée.
- Dans le fichier index.php, effectuez les actions suivantes :
- Incluez le fichier utilisateursModel.php pour accéder aux fonctions liées à la table t_utilisateurs_uti.
- Supprimez l'inclusion du fichier gestionBdd.php, car celui-ci est déjà inclus via utilisateursModel.php.
- Dans le bloc try, remplacez l'appel à la fonction obtenirobtenirConnexionBdd() par un appel à la fonction selectionnerUtilisateurParSonEmail(), en lui passant l'email claudy.focan@gmail.com.
- Affichez les informations de l'utilisateur récupérées.
- Avant d'aller plus loin, assurez-vous d'avoir une sauvegarde de la table t_utilisateurs_uti ou, à défaut, les commandes SQL nécessaires pour la recréer et y réinsérer les 3 utilisateurs.
- Nous allons tester une injection SQL en simulant une situation où l'email utilisé pour la requête provient d'un formulaire accessible aux utilisateurs. Imaginez qu'un utilisateur malintentionné saisisse l'injection suivante dans le champ email : email@bidon.com'; DROP TABLE t_utilisateur_uti; -- . Voici ce qu'il se passe :
- Terminaison prématurée de la requête : L'injection commence par email@bidon.com';, ce qui termine la première requête SQL en fermant la chaîne attendue.
- Nouvelle commande SQL : Ensuite, l'instruction DROP TABLE t_utilisateurs_uti; est exécutée, entraînant la suppression complète de la table t_utilisateurs_uti.
- Commentaire du reste de la requête : La partie -- permet de commenter tout ce qui suit dans la requête d'origine, empêchant MySQL de signaler une erreur liée à un code SQL mal formé.
- Pour tester l'injection SQL, appelez la fonction selectionnerUtilisateurParEmail() en lui passant l'injection suivante comme argument :
$email = "email@bidon.com'; DROP TABLE t_utilisateur_uti; -- "; selectionnerUtilisateurParEmail($email); - Vérifiez si votre table t_utilisateurs_uti a bien été supprimée par l'injection SQL.
- Recréez la table t_utilisateurs_uti et réinsérez les 3 utilisateurs pour les prochains tests.
- Adaptez votre fonction selectionnerUtilisateurParSonEmail() pour utiliser des requêtes préparées et ainsi prévenir les injections SQL.
- Testez à nouveau l'injection SQL pour vérifier qu'elle échoue grâce à l'utilisation de la requête préparée.
Exo-gestion-des-bases-de-donnees-04
- Objectif : Ajouter une fonction au modèle utilisateursModel pour permettre l'ajout d'un nouvel utilisateur dans la table t_utilisateur_uti.
- Instructions :
- Dupliquez le projet Exo-gestion-des-bases-de-donnees-03 et renommez-le en Exo-gestion-des-bases-de-donnees-04.
- Dans le fichier utilisateursModel.php, ajoutez la fonction ajouterUtilisateur() avec les caractéristiques suivantes :
- La fonction accepte 3 paramètres :
- $nom : Chaîne de caractères (obligatoire).
- $email : Chaîne de caractères (obligatoire).
- $prenom : Chaîne de caractères (facultative).
- Formatez l'email pour qu'il soit entièrement en lettres minuscules avant d'ajouter sa valeur à la requête.
- Utilisez une requête préparée pour éviter les injections SQL.
- La fonction accepte 3 paramètres :
- Dans le fichier index.php, remplacez le contenu actuel du bloc try par le code suivant :
- Ajoutez un nouvel utilisateur à l'aide de la fonction ajouterUtilisateur(), en simulant les données saisies par un utilisateur dans un formulaire :
- Nom : Murtin
- Prénom : Brano
- Email : mUrtin.brAno@gmail.com
- Si l'utilisateur a été ajouté avec succès, affichez le message suivant : L'utilisateur a bien été ajouté avec l'identifiant n°X. Pour récupérer l'identifiant automatiquement généré par MySQL lors du dernier INSERT effectué avec cet objet PDO, vous devez utiliser la méthode lastInsertId(). N'hésitez pas à parcourir la documentation pour obtenir plus d'informations sur le sujet.
- Vérifiez dans la table t_utilisateur_uti que cet utilisateur a bien été ajouté et que son email a été entièrement converti en minuscules.
- Ajoutez un autre utilisateur qui n'a pas souhaité saisir son prénom (facultatif), en utilisant la fonction ajouterUtilisateur() et en simulant les données saisies par un utilisateur dans un formulaire :
- Nom : Groot
- Prénom : NULL
- Email : jesappellegroot@gmail.com
- Ajoutez un nouvel utilisateur à l'aide de la fonction ajouterUtilisateur(), en simulant les données saisies par un utilisateur dans un formulaire :
- Vérifiez à nouveau dans la table t_utilisateur_uti que cet utilisateur a bien été ajouté.
Exo-gestion-des-bases-de-donnees-05
- Objectif : Afficher un message d'erreur à l'utilisateur si celui-ci tente de s'enregistrer avec une adresse email déjà utilisée dans la table t_utilisateurs_uti, plutôt que de déclencher une exception.
- Instructions :
- Dupliquez le projet Exo-gestion-des-bases-de-donnees-04 et renommez-le en Exo-gestion-des-bases-de-donnees-05.
- Ouvrez la page index.php dans votre navigateur et rechargez-la pour voir comment le programme tente actuellement d'ajouter deux fois le même utilisateur. Comme la colonne email est configurée en UNIQUE (elle n'accepte pas les doublons), une exception devrait être levée.
- Dans le fichier index.php, modifiez le contenu du bloc try pour empêcher le déclenchement de l'exception. Affichez plutôt un message indiquant que l'email est déjà utilisé, en suivant ces étapes :
- Avant d'ajouter un nouvel utilisateur, appelez la fonction selectionnerUtilisateurParSonEmail() en lui passant l'email prévu pour le nouvel utilisateur.
- Si aucun utilisateur n'est trouvé, vous pouvez ajouter le nouvel utilisateur via la fonction ajouterUtilisateur().
- Si l'utilisateur existe déjà, affichez un message d'erreur indiquant que l'adresse email est déjà utilisée.
- Essayez à nouveau d'ajouter un utilisateur avec une adresse email déjà présente dans la table. Vous devriez voir s'afficher le message d'erreur que vous venez de configurer.
- Enfin, testez l'ajout d'un nouvel utilisateur avec un email qui n'existe pas encore dans la table pour vérifier que l'insertion fonctionne toujours. Vérifiez ensuite dans la table si le nouvel utilisateur a bien été ajouté.
Exo-gestion-des-bases-de-donnees-06
- Objectif : Ajouter une fonction au modèle utilisateursModel pour permettre la mise à jour de l'email d'un utilisateur existant dans la table t_utilisateur_uti.
- Instructions :
- Dupliquez le projet Exo-gestion-des-bases-de-donnees-05 et renommez-le en Exo-gestion-des-bases-de-donnees-06.
- Dans le fichier utilisateursModel.php, ajoutez une fonction nommée actualiserEmailUtilisateur() avec les caractéristiques suivantes :
- La fonction accepte 2 paramètres :
- $id : Identifiant unique permettant de cibler l'utilisateur dont on souhaite mettre à jour l'email (obligatoire).
- $email : Chaîne de caractères représentant le nouvel email (obligatoire).
- Formatez l'email pour qu'il soit entièrement en lettres minuscules avant de l'utiliser dans la requête.
- Utilisez une requête préparée pour éviter les injections SQL.
- La fonction accepte 2 paramètres :
- Dans le fichier index.php, remplacez le contenu actuel du bloc try par le code suivant :
- Appelez la fonction actualiserEmailUtilisateur() afin de mettre à jour l'email d'un utilisateur. Par exemple, mettez à jour l'email de l'utilisateur ayant l'ID 3 avec l'email monNouvelEmail@gmail.com.
- Affichez un message de succès si la mise à jour a été effectuée avec succès.
- Vérifiez dans la table t_utilisateur_uti que l'email de l'utilisateur a bien été mis à jour et qu'il a été entièrement converti en minuscules.
- Réalisez une nouvelle tentative de mise à jour avec un email déjà utilisé par un autre utilisateur. Comme la colonne email est configurée en UNIQUE (elle n'accepte pas les doublons), une exception devrait être levée.
- Affichez un message indiquant que l'email est déjà utilisé au lieu de lever une exception, en suivant ces étapes :
- Avant d'actualiser l'email, appelez la fonction selectionnerUtilisateurParSonEmail() en lui passant l'email saisi par l'utilisateur.
- Si aucun utilisateur n'est trouvé, utilisez cet email pour actualiser l'utilisateur via la fonction actualiserEmailUtilisateur().
- Si un utilisateur avec cet email existe déjà et que son ID est différent de celui de l'utilisateur qui tente de modifier son email, affichez un message d'erreur indiquant que l'adresse email est déjà utilisée.
- Tentez d'actualiser l'email avec un email libre, et vérifiez dans la table que la modification a bien été effectuée.
- Tentez d'actualiser l'email avec un email déjà utilisé par un autre utilisateur. Vous devriez obtenir un message d'erreur indiquant que l'email est déjà utilisé.
- Enfin, tentez d'actualiser l'email avec l'email actuel de l'utilisateur qui tente la modification. Aucun message ne devrait s'afficher.
Exo-gestion-des-bases-de-donnees-07
- Objectif : Ajouter une fonction au modèle utilisateursModel pour permettre la suppression d'un utilisateur présent dans la table t_utilisateur_uti.
- Instructions :
- Dupliquez le projet Exo-gestion-des-bases-de-donnees-06 et renommez-le en Exo-gestion-des-bases-de-donnees-07.
- Dans le fichier utilisateursModel.php, ajoutez une fonction nommée supprimerUtilisateur() avec les caractéristiques suivantes :
- La fonction accepte 1 paramètre :
- $id : Identifiant unique de l'utilisateur à supprimer (obligatoire).
- Utilisez une requête préparée pour éviter les injections SQL.
- La fonction doit retourner true si la suppression a réussi et false si l'utilisateur n'existe pas. Après l'exécution de la requête, utilisez l'expression $stmt->rowCount() > 0 pour vérifier le résultat. La méthode $stmt->rowCount() renvoie le nombre de lignes affectées par la requête, donc si au moins une ligne a été supprimée, elle retournera un nombre supérieur à 0.
- La fonction accepte 1 paramètre :
- Dans le fichier index.php, remplacez le contenu actuel du bloc try par le code suivant :
- Appelez la fonction supprimerUtilisateur() afin de supprimer un utilisateur. Par exemple, supprimez l'utilisateur ayant l'ID 1.
- Affichez un message de succès si la suppression a été effectuée avec succès.
- Si aucune ligne n'est supprimée (par exemple, si l'utilisateur n'existe pas), affichez un message indiquant que l'utilisateur n'a pas été trouvé.
- Testez la fonctionnalité :
- Tentez de supprimer un utilisateur existant et vérifiez dans la table t_utilisateur_uti que la ligne correspondante a bien été supprimée.
- Tentez de supprimer un utilisateur avec un ID inexistant pour vérifier que le message "Utilisateur non trouvé" s'affiche.
Exercice : Projet Progressif 04
- Objectif : Implémenter un système d'inscription et de connexion sécurisé permettant aux utilisateurs de créer un compte, de se connecter et d'interagir avec le site.
- Instructions :
- Poursuivre le projet progressif initié lors du chapitre sur les modèles de pages dynamiques.
- Ajoutez une page d'inscription contenant :
- Un titre : Inscription.
- Un formulaire d'inscritpion avec les champs suivants :
- inscription_pseudo :
- Champ requis (required).
- Minimum 2 caractères (minlength).
- Maximum 255 caractères (maxlength).
- inscription_email :
- Champ requis (required).
- inscription_motDePasse :
- Champ requis (required).
- Minimum 8 caractères (minlength).
- Maximum 72 caractères (maxlength).
- inscription_motDePasse_confirmation :
- Champ requis (required).
- Minimum 8 caractères (minlength).
- Maximum 72 caractères (maxlength).
- inscription_pseudo :
- Ajoutez une page de connexion contenant :
- Un titre : Connexion.
- Un formulaire de connexion avec les champs suivants :
- connexion_pseudo :
- Champ requis (required).
- Minimum 2 caractères (minlength).
- Maximum 255 caractères (maxlength).
- connexion_motDePasse :
- Champ requis (required).
- Minimum 8 caractères (minlength).
- Maximum 72 caractères (maxlength).
- connexion_pseudo :
- Un lien vers la page d'inscription.
- Ajoutez la page connexion dans la navigation.
- Créez une base de données pour le projet bdd_projet_web
- Créez une table t_utilisateur_uti dans la base de données bdd_projet_web. Cette table contiendra les informations concernant les utilisteurs inscrits. Voici les colonnes qu'elle doit contenir :
- uti_id : Clé primaire.
- uti_pseudo : Chaine de caractère unique et requise de max. 255 caractères.
- uti_email : Chaine de caractère unique et requise de max. 255 caractères.
- uti_motdepasse : Données binaires de longueur variable (max. 255 caractères).
- Pour sécuriser les mots de passe, il ne faut jamais les enregistrer tels quels dans une base de données. Un mot de passe ne devrait jamais pouvoir être lu une fois enregistré.
- Pour éviter cela, on utilise un mécanisme appelé hachage. Cette opération transforme une chaîne de caractères (comme un mot de passe) en une valeur illisible. Cette transformation ne peut pas être inversée. Contrairement au chiffrement, on ne peut pas retrouver le mot de passe d'origine à partir de cette valeur.
- La fonction password_hash() permet de produire automatiquement un hachage sécurisé en PHP. Même si deux utilisateurs choisissent le même mot de passe, cette fonction générera deux hachages différents (grâce au sel).
- Lorsqu'un utilisateur tente de se connecter, la fonction password_verify() permet de comparer le mot de passe qu'il saisit avec celui enregistré dans la base de données. Elle effectue cette vérification de manière fiable et sécurisée, sans jamais avoir besoin de connaître ou retrouver le mot de passe d'origine.
- Ces fonctions sont recommandées pour toute gestion d'authentification en PHP. Elles sont maintenues par la communauté et s'adaptent automatiquement aux meilleures pratiques. Pour aller plus loin et consulter des exemples, vous pouvez explorer la documentation officielle.
- uti_compte_active : Valeur booléenne par defaut à 1 (pour le moment on active le compte dès sa création).
- uti_code_activation : Une valeur fixe de 5 caractères facultative.
- Utilisez le gestionnaire de formulaire réalisé lors du chapitre du même nom pour traiter les entrées utilisateurs du formulaire d'inscription.
- Mettez le gestionnaire de formulaire à jour pour qu'il puisse gérer les champs de confirmation ainsi que les valeurs devant être unique comme les emails ou encore les pseudo.
- Ajoutez les entrées utilisateurs valides issues du formulaire d'inscription dans la table t_utilisateur_uti.
- Gérez les tentatives de connexion au compte à partir des entrées utilisateur valides provenant du formulaire de connexion en les mettante en parallèle avec les données de la table t_utilisateur_uti (pseudo et mot de passe). Affichez un message de validation lors des tentatives de connexion qu'elles soient fructueuse ou pas.