Leçon 17 / 20

Créer le parcours de consultation

35 min PHP 8 — architecture, tests et sécurité Gratuit

Objectif : Assembler les couches du carnet pour afficher, après authentification, une liste paginée de contacts avec un rendu séparé.

Une liste de contacts ne doit pas être accessible à une personne qui n’a pas ouvert de session. Elle ne doit pas non plus charger tous les enregistrements lorsque le carnet grandit. Dans cette leçon, vous reliez le contrôle d’accès, le service métier, le dépôt et la vue pour construire le parcours de consultation.

Vous avez déjà séparé les couches et limité le travail des requêtes dans les leçons précédentes. Ici, le contrôleur vérifie l’état d’authentification, demande une page au service, puis transmet les données au rendu. La vue reçoit des données préparées ; elle ne connaît ni PDO ni les règles de persistance.

Définir le contrat de consultation

Le dépôt expose une méthode de lecture qui reçoit une taille de page et un décalage, puis renvoie uniquement les contacts nécessaires à l’affichage. Le service relaie cette demande : il ne construit pas de SQL et ne produit pas de HTML. Dans cet exemple, la page est numérotée à partir de 1 et la taille est plafonnée à 50 pour éviter une demande démesurée.

Contrat et service de consultationPHP
<?php

declare(strict_types=1);

interface DepotInterface
{
    /** @return list<array{id: int, nom: string, courriel: string, telephone: string}> */
    public function lister(int $limite, int $decalage): array;
}

final class ContactService
{
    public function __construct(private DepotInterface $depot)
    {
    }

    /** @return list<array{id: int, nom: string, courriel: string, telephone: string}> */
    public function lister(int $page, int $taillePage = 20): array
    {
        $page = max(1, $page);
        $taillePage = min(50, max(1, $taillePage));
        $decalage = ($page - 1) * $taillePage;

        return $this->depot->lister($taillePage, $decalage);
    }
}

Le dépôt PDO doit appliquer ces valeurs à sa requête préparée et renvoyer les colonnes utilisées par la page. Le tri stable par nom puis identifiant évite que des contacts de même nom changent arbitrairement de position entre deux pages. La pagination par LIMIT et OFFSET a été présentée dans la leçon précédente ; vous l’intégrez ici au parcours sans en reprendre les détails.

Implémentation PDO du dépôtPHP
<?php

declare(strict_types=1);

final class ContactDepot implements DepotInterface
{
    public function __construct(private PDO $pdo)
    {
    }

    public function lister(int $limite, int $decalage): array
    {
        $requete = $this->pdo->prepare(
            'SELECT id, nom, courriel, telephone
             FROM contacts
             ORDER BY nom ASC, id ASC
             LIMIT :limite OFFSET :decalage'
        );
        $requete->bindValue(':limite', $limite, PDO::PARAM_INT);
        $requete->bindValue(':decalage', $decalage, PDO::PARAM_INT);
        $requete->execute();

        return $requete->fetchAll(PDO::FETCH_ASSOC);
    }
}

Protéger et coordonner la requête

Le contrôleur constitue le point d’entrée HTTP de ce parcours. Il refuse l’accès à la liste lorsque la session ne porte pas l’indicateur d’authentification, puis lit le numéro de page, appelle le service et transmet les données à la fonction de rendu. Le code ci-dessous suppose que le point de composition a déjà injecté le service et que la session a été ouverte par le mécanisme de sécurité du projet.

Action du contrôleur et fonction de renduPHP
<?php

declare(strict_types=1);

final class CarnetControleur
{
    public function __construct(private ContactService $contacts)
    {
    }

    public function consulter(): void
    {
        if (($_SESSION['authentifie'] ?? false) !== true) {
            http_response_code(401);
            $donnees = ['authentifie' => false];
            require dirname(__DIR__) . '/Vue/rendu.php';
            return;
        }

        $page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT);
        $contacts = $this->contacts->lister(is_int($page) ? $page : 1);

        $donnees = [
            'authentifie' => true,
            'contacts' => $contacts,
            'page' => max(1, is_int($page) ? $page : 1),
        ];
        require dirname(__DIR__) . '/Vue/rendu.php';
    }
}

// src/Vue/rendu.php
<?php

declare(strict_types=1);

require dirname(__DIR__, 2) . '/templates/carnet.php';

Rendre seulement l’état utile

Le gabarit complet conserve la structure de référence, mais le serveur n’émet que la section correspondant à l’état de session. Pour un utilisateur authentifié, chaque contact est échappé au moment de l’affichage. Le code garde les attributs et les classes définis par le contrat du projet, sans ajouter de structure CSS.

Gabarit de consultationPHP
<?php

declare(strict_types=1);

$echapper = static fn (string $valeur): string => htmlspecialchars(
    $valeur,
    ENT_QUOTES | ENT_SUBSTITUTE,
    'UTF-8'
);
?>
<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Carnet de contacts privé</title>
  <link rel="stylesheet" href="/style.css">
</head>
<body class="atelier-page">
  <header class="atelier-entete">
    <a class="atelier-entete__titre" href="/">Carnet de contacts</a>
  </header>
  <main class="atelier-contenu">
    <?php if (!$donnees['authentifie']): ?>
      <section class="atelier-authentification" aria-labelledby="titre-authentification">
        <h1 id="titre-authentification">Accéder au carnet</h1>
        <p>Authentification requise.</p>
      </section>
    <?php else: ?>
      <section class="atelier-carnet" aria-labelledby="titre-carnet">
        <h1 id="titre-carnet">Mes contacts</h1>
        <section class="atelier-liste" aria-labelledby="titre-liste">
          <h2 id="titre-liste">Résultats — page <?= (int) $donnees['page'] ?></h2>
          <ul class="atelier-liste__contacts">
            <?php foreach ($donnees['contacts'] as $contact): ?>
              <li class="atelier-contact">
                <div class="atelier-contact__details">
                  <h3 class="atelier-contact__nom"><?= $echapper($contact['nom']) ?></h3>
                  <p class="atelier-contact__information"><?= $echapper($contact['courriel']) ?> · <?= $echapper($contact['telephone']) ?></p>
                </div>
              </li>
            <?php endforeach; ?>
          </ul>
        </section>
      </section>
    <?php endif; ?>
  </main>
  <footer class="atelier-pied"><p>Carnet privé — accès réservé</p></footer>
</body>
</html>

Dans votre point de composition, construisez ContactDepot avec la connexion PDO, injectez-le dans ContactService, puis injectez ce service dans CarnetControleur. La route de consultation appelle consulter() ; elle doit aussi démarrer la session via le composant de session déjà prévu par le projet. Pour vérifier le parcours, testez au minimum la réponse non authentifiée et la présence des contacts attendus après authentification, en vous appuyant sur l’organisation PHPUnit déjà en place.

À retenir

  • Contrôlez l’authentification avant de solliciter le service ou le dépôt.
  • Faites porter la pagination par le service et le dépôt, pas par la vue.
  • Transmettez à la vue uniquement les données utiles à la page.
  • Échappez les valeurs au rendu et n’exposez pas les données à une session non authentifiée.

Dans la prochaine leçon, vous ajouterez les parcours de gestion des contacts avec leurs formulaires et leurs protections associées.

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.