Leçon 14 / 15

Réaliser les opérations du carnet

35 min PHP 8 — structurer une application (PDO, POO, sessions) Gratuit

Objectif : Relier les formulaires aux traitements et afficher la liste des contacts avec leurs actions.

Une page privée qui s’affiche ne suffit pas : les formulaires doivent déclencher les opérations du carnet, et leurs résultats doivent revenir à l’écran. Dans cette leçon, vous raccordez la liste, la recherche, l’ajout, la modification et la suppression au dépôt déjà préparé.

Vous avez déjà vu le rôle du contrôleur, du dépôt et du gabarit dans les leçons précédentes. Ici, le point d’entrée appelle le contrôleur de contacts après avoir vérifié la session ; le dépôt exécute les requêtes préparées, et les gabarits restent consacrés à l’affichage.

Traiter une action dans le contrôleur

Le contrôleur reçoit le dépôt par son constructeur, comme prévu pour les responsabilités de stockage. Sa méthode `traiter()` choisit une vue selon l’action demandée, récupère la recherche ou l’identifiant, puis renvoie les variables que le gabarit doit afficher. Les opérations POST réussies redirigent vers la liste afin qu’un rafraîchissement ne renvoie pas le formulaire.

src/Controleur/ContactControleur.phpPHP
<?php

declare(strict_types=1);

namespace Carnet\Controleur;

use Carnet\Depot\ContactDepot;
use Carnet\Modele\Contact;
use InvalidArgumentException;

final class ContactControleur
{
    public function __construct(private ContactDepot $depot)
    {
    }

    public function traiter(string $action, array $donnees): array
    {
        if ($action === 'ajouter' && ($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
            $this->depot->ajouter($this->creerContact($donnees));
            return ['redirection' => '/'];
        }

        if ($action === 'modifier' && ($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
            $id = $this->identifiant($donnees['id'] ?? null);
            $this->depot->modifier($this->creerContact($donnees, $id));
            return ['redirection' => '/'];
        }

        if ($action === 'supprimer' && ($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
            $this->depot->supprimer($this->identifiant($donnees['id'] ?? null));
            return ['redirection' => '/'];
        }

        if ($action === 'ajouter') {
            return ['vue' => 'contact-formulaire.php', 'titre' => 'Ajouter un contact', 'action' => 'ajouter', 'contact' => []];
        }

        if ($action === 'modifier') {
            $id = $this->identifiant($donnees['id'] ?? null);
            $contact = $this->depot->trouver($id);
            if ($contact === null) {
                return ['vue' => 'erreur.php', 'titre' => 'Contact introuvable', 'message' => 'Ce contact n’existe pas.'];
            }
            return ['vue' => 'contact-formulaire.php', 'titre' => 'Modifier un contact', 'action' => 'modifier', 'contact' => $contact->versTableau()];
        }

        $recherche = trim((string) ($donnees['recherche'] ?? ''));
        $contacts = $this->depot->rechercher($recherche);
        $lignes = array_map(static fn (Contact $contact): array => $contact->versTableau(), $contacts);

        return ['vue' => 'contacts.php', 'titre' => 'Mes contacts', 'contacts' => $lignes, 'recherche' => $recherche];
    }

    private function creerContact(array $donnees, ?int $id = null): Contact
    {
        return new Contact(
            $id,
            trim((string) ($donnees['nom'] ?? '')),
            trim((string) ($donnees['courriel'] ?? '')) ?: null,
            trim((string) ($donnees['telephone'] ?? '')) ?: null
        );
    }

    private function identifiant(mixed $valeur): int
    {
        $id = filter_var($valeur, FILTER_VALIDATE_INT);
        if ($id === false || $id < 1) {
            throw new InvalidArgumentException('Identifiant de contact invalide.');
        }
        return $id;
    }
}

Raccorder les formulaires et la liste

Le gabarit des contacts reçoit des tableaux préparés par le contrôleur ; il n’appelle jamais le dépôt. Les valeurs issues de la recherche et de la base sont échappées à l’affichage, et les identifiants sont convertis en entiers dans les liens et champs cachés.

templates/contacts.phpPHP
<?php

declare(strict_types=1);

/** @var array<int, array{id:int, nom:string, courriel:?string, telephone:?string}> $contacts */
/** @var string $recherche */
?>
<h1>Mes contacts</h1>
<form class="carnet-formulaire" action="/" method="get">
    <label class="carnet-champ">Rechercher
        <input name="recherche" value="<?= htmlspecialchars($recherche, ENT_QUOTES, 'UTF-8') ?>">
    </label>
    <div class="carnet-actions">
        <button class="carnet-bouton" type="submit">Rechercher</button>
    </div>
</form>
<ul class="carnet-liste">
    <?php foreach ($contacts as $contact): ?>
        <li class="carnet-contact">
            <h2><?= htmlspecialchars($contact['nom'], ENT_QUOTES, 'UTF-8') ?></h2>
            <p><?= htmlspecialchars($contact['courriel'] ?? '', ENT_QUOTES, 'UTF-8') ?></p>
            <p><?= htmlspecialchars($contact['telephone'] ?? '', ENT_QUOTES, 'UTF-8') ?></p>
            <div class="carnet-actions">
                <a href="/?action=modifier&id=<?= (int) $contact['id'] ?>">Modifier</a>
                <form action="/" method="post" onsubmit="return confirm('Supprimer ce contact ?')">
                    <input type="hidden" name="action" value="supprimer">
                    <input type="hidden" name="id" value="<?= (int) $contact['id'] ?>">
                    <button class="carnet-bouton" type="submit">Supprimer</button>
                </form>
            </div>
        </li>
    <?php endforeach; ?>
</ul>

Le formulaire de suppression demande une confirmation dans le navigateur avant l’envoi ; cette confirmation améliore le parcours, mais ne remplace pas le contrôle serveur de l’identifiant. Le gabarit d’ajout et de modification utilise les mêmes champs du contrat et transmet une action POST ainsi que l’identifiant pour la modification.

templates/contact-formulaire.phpPHP
<?php

declare(strict_types=1);

/** @var string $action */
/** @var array{id?:int, nom?:string, courriel:?string, telephone:?string} $contact */
?>
<h1><?= $action === 'ajouter' ? 'Ajouter un contact' : 'Modifier un contact' ?></h1>
<form class="carnet-formulaire" action="/" method="post">
    <input type="hidden" name="action" value="<?= htmlspecialchars($action, ENT_QUOTES, 'UTF-8') ?>">
    <input type="hidden" name="id" value="<?= htmlspecialchars((string) ($contact['id'] ?? ''), ENT_QUOTES, 'UTF-8') ?>">
    <label class="carnet-champ">Nom
        <input name="nom" value="<?= htmlspecialchars($contact['nom'] ?? '', ENT_QUOTES, 'UTF-8') ?>" required>
    </label>
    <label class="carnet-champ">Courriel
        <input type="email" name="courriel" value="<?= htmlspecialchars($contact['courriel'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
    </label>
    <label class="carnet-champ">Téléphone
        <input type="tel" name="telephone" value="<?= htmlspecialchars($contact['telephone'] ?? '', ENT_QUOTES, 'UTF-8') ?>">
    </label>
    <div class="carnet-actions">
        <button class="carnet-bouton" type="submit">Enregistrer</button>
        <a href="/">Annuler</a>
    </div>
</form>

Dans `public/index.php`, gardez le traitement de connexion et le contrôle de session déjà en place. Pour une requête privée, créez `ContactDepot` avec la connexion PDO, appelez `ContactControleur::traiter()` avec `$_GET` pour une lecture ou `$_POST` pour une écriture, puis rendez le gabarit indiqué dans le résultat au moyen du gabarit commun. Si le résultat contient `redirection`, envoyez l’en-tête `Location` puis arrêtez le script avant tout affichage.

Les données utilisées ici reprennent le contrat HTML défini pour le carnet et les méthodes de dépôt étudiées dans les leçons PDO. Si une opération soulève une exception, transmettez le message au gabarit d’erreur plutôt que d’imprimer depuis le contrôleur ; le parcours de test et les cas d’erreur seront vérifiés dans la prochaine leçon.

À retenir

  • Le contrôleur traduit l’action reçue en appel au dépôt et prépare les données à afficher.
  • Les requêtes d’écriture utilisent POST ; après réussite, redirigez vers la liste.
  • Validez l’identifiant côté serveur, même si le formulaire le transmet dans un champ caché.
  • Le gabarit affiche les résultats et échappe les valeurs textuelles avant de les intégrer au HTML.
  • La confirmation de suppression côté navigateur ne remplace jamais le traitement serveur.

À la prochaine leçon, vous parcourrez les opérations du carnet avec des scénarios de test et corrigerez les défauts repérés.

Envie d'aller plus loin ?

Les cours Enovatic sont gratuits et le resteront. Si vous cherchez un accompagnement encadré, nos formations professionnelles sont animées par les mêmes formateurs.