Construire la plateforme Decoupe
Cette série d’exercice vise à construire Decoupe, une plateforme qui répertorie des salons de coiffure. On avance par étapes : d’abord les pages du site générées côté serveur, pour terminer avec une API qui expose les mêmes données en JSON.
Chaque exercice réutilise le précédent, alors conservez votre code d’une étape à l’autre.
Le travail sera fait dans un dossier
decoupe, à créer dans le dossier XAMPPhtdocs.
Les données
Section intitulée « Les données »Toutes les pages s’appuient sur le même fichier de données, fourni ci-dessous. Il retourne un tableau de douze salons.
Chaque salon possède un identifiant, un nom, une ville, une note, un indicateur de promotion, un horaire et une liste de services. Chaque service précise son nom, un court descriptif, un prix de base et sa clientèle (homme, femme ou mixte).
<?phpreturn [ [ 'id' => 1, 'nom' => 'Studio Ciseaux', 'ville' => 'Montréal', 'note' => 4.7, 'promotion' => true, 'horaire' => 'Mardi au samedi, 9 h à 18 h', 'services' => [ ['nom' => 'Coupe homme', 'description' => 'Coupe classique aux ciseaux', 'prix_base' => 25, 'clientele' => 'homme'], ['nom' => 'Coloration', 'description' => 'Couleur complète', 'prix_base' => 60, 'clientele' => 'femme'], ['nom' => 'Taille de barbe','description' => 'Contour et entretien', 'prix_base' => 18, 'clientele' => 'homme'], ], ], [ 'id' => 2, 'nom' => 'La Belle Mèche', 'ville' => 'Québec', 'note' => 4.9, 'promotion' => false, 'horaire' => 'Lundi au vendredi, 10 h à 19 h', 'services' => [ ['nom' => 'Coupe femme', 'description' => 'Coupe et brushing', 'prix_base' => 45, 'clientele' => 'femme'], ['nom' => 'Mise en plis', 'description' => 'Coiffure pour occasion', 'prix_base' => 35, 'clientele' => 'femme'], ], ], [ 'id' => 3, 'nom' => 'Tif et Style', 'ville' => 'Montréal', 'note' => 4.3, 'promotion' => true, 'horaire' => 'Mardi au samedi, 9 h à 17 h', 'services' => [ ['nom' => 'Coupe homme', 'description' => 'Coupe tendance', 'prix_base' => 22, 'clientele' => 'homme'], ['nom' => 'Rasage traditionnel','description' => 'Rasage à la serviette chaude', 'prix_base' => 30, 'clientele' => 'homme'], ], ], [ 'id' => 4, 'nom' => 'Coupe et Compagnie', 'ville' => 'Laval', 'note' => 4.1, 'promotion' => false, 'horaire' => 'Lundi au samedi, 9 h à 20 h', 'services' => [ ['nom' => 'Coupe enfant', 'description' => 'Pour les 12 ans et moins', 'prix_base' => 18, 'clientele' => 'mixte'], ['nom' => 'Coupe femme', 'description' => 'Coupe simple', 'prix_base' => 40, 'clientele' => 'femme'], ], ], [ 'id' => 5, 'nom' => "L'Atelier du Cheveu", 'ville' => 'Longueuil', 'note' => 4.6, 'promotion' => true, 'horaire' => 'Mardi au vendredi, 9 h à 18 h', 'services' => [ ['nom' => 'Balayage', 'description' => 'Éclaircissement progressif', 'prix_base' => 90, 'clientele' => 'femme'], ['nom' => 'Coupe femme', 'description' => 'Coupe et coiffage', 'prix_base' => 42, 'clientele' => 'femme'], ], ], [ 'id' => 6, 'nom' => 'Barbe et Lame', 'ville' => 'Gatineau', 'note' => 4.8, 'promotion' => false, 'horaire' => 'Lundi au vendredi, 8 h à 17 h', 'services' => [ ['nom' => 'Rasage traditionnel','description' => 'Rasage complet au coupe-chou', 'prix_base' => 32, 'clientele' => 'homme'], ['nom' => 'Taille de barbe', 'description' => 'Mise en forme et huile', 'prix_base' => 20, 'clientele' => 'homme'], ], ], [ 'id' => 7, 'nom' => 'Chez Manon', 'ville' => 'Sherbrooke', 'note' => 4.2, 'promotion' => false, 'horaire' => 'Mardi au samedi, 9 h à 18 h', 'services' => [ ['nom' => 'Coupe femme', 'description' => 'Coupe et brushing', 'prix_base' => 38, 'clientele' => 'femme'], ['nom' => 'Coloration', 'description' => 'Retouche de racines', 'prix_base' => 55, 'clientele' => 'femme'], ], ], [ 'id' => 8, 'nom' => 'Le Salon Nordique', 'ville' => 'Trois-Rivières', 'note' => 4.5, 'promotion' => true, 'horaire' => 'Mercredi au dimanche, 10 h à 18 h', 'services' => [ ['nom' => 'Coupe homme', 'description' => 'Coupe et coiffage', 'prix_base' => 26, 'clientele' => 'homme'], ['nom' => 'Coupe enfant', 'description' => 'Pour les 12 ans et moins', 'prix_base' => 16, 'clientele' => 'mixte'], ], ], [ 'id' => 9, 'nom' => 'Coiffure Éclat', 'ville' => 'Montréal', 'note' => 4.4, 'promotion' => false, 'horaire' => 'Lundi au vendredi, 9 h à 19 h', 'services' => [ ['nom' => 'Mise en plis', 'description' => 'Coiffure pour occasion', 'prix_base' => 34, 'clientele' => 'femme'], ['nom' => 'Balayage', 'description' => 'Reflets sur mesure', 'prix_base' => 85, 'clientele' => 'femme'], ], ], [ 'id' => 10, 'nom' => 'Tendance Studio', 'ville' => 'Laval', 'note' => 4.0, 'promotion' => true, 'horaire' => 'Mardi au samedi, 10 h à 18 h', 'services' => [ ['nom' => 'Coupe femme', 'description' => 'Coupe moderne', 'prix_base' => 39, 'clientele' => 'femme'], ['nom' => 'Coupe homme', 'description' => 'Coupe et dégradé', 'prix_base' => 24, 'clientele' => 'homme'], ], ], [ 'id' => 11, 'nom' => 'La Griffe', 'ville' => 'Québec', 'note' => 4.7, 'promotion' => false, 'horaire' => 'Lundi au samedi, 9 h à 18 h', 'services' => [ ['nom' => 'Coloration', 'description' => 'Couleur complète', 'prix_base' => 62, 'clientele' => 'femme'], ['nom' => 'Coupe femme', 'description' => 'Coupe et brushing', 'prix_base' => 44, 'clientele' => 'femme'], ], ], [ 'id' => 12, 'nom' => "Ciseaux d'Or", 'ville' => 'Montréal', 'note' => 4.9, 'promotion' => true, 'horaire' => 'Mardi au dimanche, 9 h à 20 h', 'services' => [ ['nom' => 'Coupe homme', 'description' => 'Coupe et coiffage', 'prix_base' => 28, 'clientele' => 'homme'], ['nom' => 'Rasage traditionnel','description' => 'Rasage à chaud', 'prix_base' => 33, 'clientele' => 'homme'], ['nom' => 'Balayage', 'description' => 'Éclaircissement doux', 'prix_base' => 95, 'clientele' => 'femme'], ], ],];Rappels utiles
Section intitulée « Rappels utiles »Utilisation de htmlspecialchars
Section intitulée « Utilisation de htmlspecialchars »Toute donnée affichée dans une page doit être échappée avec htmlspecialchars() : un nom de salon, une ville, un service, ou toute valeur venue des données ou du client.
Fonctions utilitaires
Section intitulée « Fonctions utilitaires »| Fonction | Rôle |
|---|---|
htmlspecialchars($texte) | Échapper une valeur avant de l’insérer dans du HTML. |
number_format($n, 2, ',', ' ') | Afficher un prix avec deux décimales, une virgule et un espace pour les milliers. |
array_filter($liste, $test) | Ne garder que les éléments qui satisfont une condition. |
array_values($liste) | Réindexer une liste après un filtrage. |
count($liste) | Compter les éléments d’un tableau. |
trim($texte) | Retirer les espaces au début et à la fin d’une chaîne. |
json_encode et json_decode | Convertir entre un tableau PHP et du texte JSON. |
Niveau 1 : Les pages du site
Section intitulée « Niveau 1 : Les pages du site »Exercice 1 – La liste des salons
Section intitulée « Exercice 1 – La liste des salons »Créez la page salons.php. La page charge les données présentées plus haut,
puis affiche chaque salon avec son nom, sa ville et sa note. Le nom est un lien
vers la page de détail, salon.php?id=..., que vous construirez à l’exercice suivant.
Échappez toute donnée affichée.
Vérification Indice
require renvoie le tableau des salons. Parcourez-le avec foreach. Pour le lien, insérez l’identifiant dans l’URL : salon.php?id=<?= $salon['id'] ?>.
Exercice 2 – Le détail d’un salon
Section intitulée « Exercice 2 – Le détail d’un salon »Créez maintenant la page salon.php. La page lit l’identifiant passé en paramètre
et affiche le salon correspondant : nom, ville, note, horaire, et la liste des
services avec leur descriptif, leur prix et leur clientèle.
Écrivez d’abord une fonction qui retrouve un salon par son identifiant, puis affichez
un formulaire de prise de rendez-vous (méthode POST) demandant le nom du client et
une date. Si l’identifiant ne correspond à aucun salon, affichez un message et renvoyez un statut 404 (avec http_response_code).
Signature : trouverSalon(array $salons, int $id): ?array
Vérification Indice
La fonction reprend le patron de recherche : un foreach, un return dès qu’un id correspond, et return null à la fin. Convertissez $_GET['id'] en entier avec (int). Pour le prix, number_format donne un affichage soigné. Le formulaire renvoie vers la même page, donc son action conserve l’identifiant.
Exercice 3 – Les salons en promotion
Section intitulée « Exercice 3 – Les salons en promotion »Créez la page d’entrée du site accueil.php. Elle met en avant seulement les salons en promotion.
Écrivez une fonction qui reçoit tous les salons et retourne uniquement ceux dont la promotion est active, puis affichez le résultat.
Signature : salonsEnPromotion(array $salons): array
Vérification Indice
Parcourez les salons et ajoutez au résultat ceux dont promotion vaut true.
La fonction array_filter fait la même chose en une expression, avec une fonction fléchée :
array_filter($salons, fn($s) => $s['promotion']).
Exercice 4 – Optimiser avec des fragments réutilisables
Section intitulée « Exercice 4 – Optimiser avec des fragments réutilisables »Les pages salons.php et accueil.php répètent le même affichage de salon, et chaque page répétera bientôt la même structure de document (l’en-tête et le pied).
Optimisez le site en extrayant des fragments réutilisables :
- un layout :
fragments/entete.php, qui ouvre la page à partir d’un titre, etfragments/pied.php, qui la referme ; - une carte de salon :
fragments/carte-salon.php, qui affiche un salon à partir d’une variable$salon.
Réécrivez ensuite les pages salons.php et accueil.php pour qu’elles incluent ces fragments au lieu de répéter le HTML.
Vérification Indice
include insère un fragment à l’endroit voulu. Un fragment voit les variables définies avant son inclusion : entete.php utilise $titre, et carte-salon.php utilise $salon.
Dans la boucle, on définit $salon à chaque tour (le foreach s’en charge), puis on inclut la carte.
Niveau 2 : L’API en JSON
Section intitulée « Niveau 2 : L’API en JSON »Exercice 5 – La liste des salons en JSON
Section intitulée « Exercice 5 – La liste des salons en JSON »API
Créez la page api/salons.php. Ce point d’accès retourne la liste des salons en JSON, pour un programme qui souhaite les données sans la présentation. Annoncez le format avec l’en-tête approprié.
api/, alors que les données sont dans data/. Utilisez __DIR__ pour un chemin fiable.
Vérification Indice
header('Content-Type: application/json; charset=utf-8') annonce le format, puis json_encode produit le JSON.
Depuis api/salons.php, le fichier de données se trouve à __DIR__ . '/../data/salons.php'. L’option JSON_UNESCAPED_UNICODE garde les accents lisibles.
Exercice 6 Filtrer avec des paramètres de requête
Section intitulée « Exercice 6 Filtrer avec des paramètres de requête »API et paramètres
Enrichissez api/salons.php pour qu’il accepte deux filtres dans la chaîne de requête : ?ville=Montréal ne retourne que les salons de cette ville, et ?promo=1 ne retourne que les salons en promotion. Les deux filtres peuvent se combiner.
Vérification Indice
Lisez les filtres dans $_GET. Appliquez array_filter sur la liste pour chaque filtre présent. Après un array_filter, les clés d’origine sont conservées, ce qui produirait un objet JSON plutôt qu’un tableau : array_values réindexe la liste avant l’encodage.
Exercice 7 – Enregistrer un salon avec POST
Section intitulée « Exercice 7 – Enregistrer un salon avec POST »API et POST
Ajoutez la prise en charge de la méthode POST à api/salons.php. Une requête POST transporte un salon en JSON dans son corps. Lisez ce corps, vérifiez que le nom et la ville sont présents, puis répondez avec le statut 201 et le salon créé. Si les données sont incomplètes, répondez avec le statut 400. Une requête GET conserve le comportement des exercices précédents.
Vérification Indice
Consultez $_SERVER['REQUEST_METHOD'] pour aiguiller.
Un corps JSON se lit avec file_get_contents('php://input'), puis json_decode(..., true).
Vérifiez les champs avec isset avant de construire le salon. Attribuez un nouvel identifiant, par exemple count($salons) + 1.