Ajouter, modifier et supprimer un contact
Objectif : Ajouter, modifier et supprimer un contact en validant chaque formulaire et en protégeant les modifications.
Un contact peut être ajouté deux fois si vous rechargez une page après l’envoi du formulaire. Une modification peut viser la mauvaise personne si elle s’appuie sur sa position dans la liste, et une suppression doit être protégée comme toute autre écriture. Vous allez construire les trois traitements autour d’un identifiant stable, de la validation serveur et du jeton CSRF déjà mis en place.
Les pages privées démarrent la session et contrôlent l’authentification avant toute sortie HTML, comme dans la leçon sur la protection par mot de passe. Les exemples ci-dessous réutilisent les fonctions de stockage et de CSRF déjà présentes dans includes/fonctions.php : chargerContacts(), enregistrerContacts(), creerJetonCsrf() et jetonCsrfValide(). Elles doivent fournir respectivement la liste ou un échec de lecture, enregistrer une liste complète, obtenir le jeton de session et vérifier le jeton soumis.
Utiliser un identifiant stable
Chaque contact doit porter une clé id qui ne change pas lorsque vous triez ou filtrez la liste. Lors d’un ajout, créez cet identifiant côté serveur ; lors d’une modification ou d’une suppression, retrouvez le contact par cette clé, jamais par son numéro dans un tableau. Le format utilisé ici est une chaîne produite par bin2hex(random_bytes(16)), suffisamment pratique pour identifier chaque entrée du fichier JSON.
<?php
$contact = [
'id' => bin2hex(random_bytes(16)),
'nom' => $nom,
'courriel' => $courriel,
'telephone' => $telephone,
];
$contacts[] = $contact;
enregistrerContacts($contacts);Dans votre traitement d’ajout existant, construisez le contact seulement après validation des champs et du jeton CSRF. Si l’enregistrement échoue, n’affichez pas un succès : gardez la page et ses données de formulaire pour montrer une erreur compréhensible. Si l’écriture réussit, redirigez vers index.php?message=ajoute avec un statut de redirection temporaire, puis arrêtez immédiatement le script.
Préremplir et traiter la modification
Dans contact-modifier.php, récupérez l’identifiant transmis dans l’URL pour afficher le formulaire, puis cherchez le contact correspondant dans la liste chargée. Si aucun contact ne correspond, ne produisez pas de formulaire qui pourrait modifier une autre entrée. Le gabarit reprend les valeurs du contact et son identifiant ; échappez ces valeurs à leur affichage comme vous le faites déjà pour la liste.
<?php
declare(strict_types=1);
session_start();
require __DIR__ . '/includes/fonctions.php';
verifierAcces();
$contacts = chargerContacts();
$erreurs = [];
$identifiant = (string) ($_POST['id'] ?? $_GET['id'] ?? '');
$indexContact = null;
foreach ($contacts as $index => $contact) {
if (($contact['id'] ?? '') === $identifiant) {
$indexContact = $index;
break;
}
}
if ($indexContact === null) {
http_response_code(404);
$message = 'Contact introuvable.';
} elseif (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') {
if (!jetonCsrfValide($_POST['jeton_csrf'] ?? null)) {
$erreurs['formulaire'] = 'La demande a expiré. Rechargez le formulaire.';
} else {
$nom = trim((string) ($_POST['nom'] ?? ''));
$courriel = trim((string) ($_POST['courriel'] ?? ''));
$telephone = trim((string) ($_POST['telephone'] ?? ''));
if ($nom === '' || strlen($nom) > 100) {
$erreurs['nom'] = 'Saisissez un nom de 100 caractères au maximum.';
}
if (filter_var($courriel, FILTER_VALIDATE_EMAIL) === false) {
$erreurs['courriel'] = 'Saisissez une adresse e-mail valide.';
}
if ($erreurs === []) {
$contacts[$indexContact]['nom'] = $nom;
$contacts[$indexContact]['courriel'] = $courriel;
$contacts[$indexContact]['telephone'] = $telephone;
if (enregistrerContacts($contacts)) {
header('Location: index.php?message=modifie', true, 303);
exit;
}
$erreurs['formulaire'] = 'Impossible d’enregistrer la modification.';
}
}
}
$contact = $contacts[$indexContact] ?? null;
$jetonCsrf = creerJetonCsrf();
require __DIR__ . '/includes/entete.php';
?>
<main class="carnet-principal">
<?php if ($contact !== null): ?>
<form class="carnet-formulaire" action="contact-modifier.php?id=<?= htmlspecialchars($contact['id'], ENT_QUOTES, 'UTF-8') ?>" method="post">
<input type="hidden" name="id" value="<?= htmlspecialchars($contact['id'], ENT_QUOTES, 'UTF-8') ?>">
<input type="hidden" name="jeton_csrf" value="<?= htmlspecialchars($jetonCsrf, ENT_QUOTES, 'UTF-8') ?>">
<label class="carnet-champ" for="nom">Nom</label>
<input id="nom" name="nom" value="<?= htmlspecialchars((string) ($nom ?? $contact['nom']), ENT_QUOTES, 'UTF-8') ?>" required>
<label class="carnet-champ" for="courriel">Adresse e-mail</label>
<input id="courriel" name="courriel" type="email" value="<?= htmlspecialchars((string) ($courriel ?? $contact['courriel']), ENT_QUOTES, 'UTF-8') ?>" required>
<label class="carnet-champ" for="telephone">Téléphone</label>
<input id="telephone" name="telephone" type="tel" value="<?= htmlspecialchars((string) ($telephone ?? $contact['telephone']), ENT_QUOTES, 'UTF-8') ?>">
<div class="carnet-actions"><button type="submit">Enregistrer</button></div>
</form>
<?php else: ?>
<p class="carnet-message carnet-message--erreur" role="alert">Contact introuvable.</p>
<?php endif; ?>
</main>
<?php require __DIR__ . '/includes/pied.php';L’exemple montre la séquence essentielle du traitement : le contact est retrouvé par identifiant, les données sont contrôlées, puis seules ses valeurs éditables sont remplacées. Dans votre projet, conservez le rendu des erreurs par champ déjà construit dans la leçon précédente et fournissez au gabarit le jeton CSRF. Le code de rendu ci-dessus illustre la structure du formulaire ; gardez également l’en-tête et le pied partagés et n’incluez pas une seconde balise main si votre en-tête partagé ouvre déjà cette zone.
Supprimer uniquement sur POST
La suppression ne doit jamais être déclenchée par un simple lien GET. Dans chaque élément carnet-contact, utilisez un formulaire POST contenant l’identifiant et le jeton CSRF ; vous pouvez demander une confirmation dans le navigateur avant l’envoi, mais cette confirmation ne remplace pas les contrôles serveur. La page contact-supprimer.php vérifie la méthode, le jeton et l’existence de l’identifiant avant de retirer l’entrée.
<?php
declare(strict_types=1);
session_start();
require __DIR__ . '/includes/fonctions.php';
verifierAcces();
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
http_response_code(405);
exit('Méthode non autorisée.');
}
$identifiant = (string) ($_POST['id'] ?? '');
if (!jetonCsrfValide($_POST['jeton_csrf'] ?? null)) {
http_response_code(403);
exit('Demande refusée.');
}
$contacts = chargerContacts();
$contactsRestants = array_values(array_filter(
$contacts,
static fn (array $contact): bool => ($contact['id'] ?? '') !== $identifiant
));
if ($identifiant === '' || count($contactsRestants) === count($contacts)) {
http_response_code(404);
exit('Contact introuvable.');
}
if (!enregistrerContacts($contactsRestants)) {
http_response_code(500);
exit('Impossible de supprimer le contact.');
}
header('Location: index.php?message=supprime', true, 303);
exit;Le code de suppression remplace la liste par les contacts dont l’identifiant est différent, puis réindexe le tableau avant l’enregistrement. Le statut 303 demande au navigateur de charger la page de destination en GET après un POST ; un rechargement ne répète donc pas directement la modification. Affichez dans index.php un message de succès correspondant au paramètre reçu, après l’avoir reconnu parmi des valeurs prévues telles que ajoute, modifie ou supprime.
À retenir
La prochaine leçon vous permettra de parcourir le carnet complet et de vérifier ses principaux parcours d’utilisation.