Leçon 4 / 20

Assembler les dépendances explicitement

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

Objectif : Construire le dépôt PDO et les services applicatifs à partir de dépendances injectées.

Un service qui ouvre lui-même une connexion à la base est difficile à faire évoluer : il dépend à la fois des règles métier et d’un détail d’infrastructure. Vous allez rendre ces dépendances visibles, puis les assembler au démarrage de l’application. Le service pourra ainsi utiliser le contrat du dépôt déjà défini, tandis que l’implémentation PDO restera dans la couche de persistance.

Rendre la dépendance visible dans le service

L’injection par constructeur consiste à recevoir une dépendance au moment où un objet est créé, au lieu de la fabriquer à l’intérieur de cet objet. Ici, ContactService reçoit DepotInterface : il ne connaît pas PDO et ne décide pas comment les contacts sont stockés. Le contrat présenté dans la leçon précédente reste la référence pour les signatures utilisées ci-dessous : lister, rechercher, trouver, enregistrer et supprimer.

src/Service/ContactService.phpPHP
<?php

declare(strict_types=1);

namespace Atelier\Service;

use Atelier\Depot\DepotInterface;
use InvalidArgumentException;

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

    public function lister(): array
    {
        return $this->depot->lister();
    }

    public function rechercher(string $terme): array
    {
        return $this->depot->rechercher(trim($terme));
    }

    public function enregistrer(array $donnees): int
    {
        $nom = trim((string) ($donnees['nom'] ?? ''));
        $courriel = trim((string) ($donnees['courriel'] ?? ''));
        $telephone = trim((string) ($donnees['telephone'] ?? ''));

        if ($nom === '' || filter_var($courriel, FILTER_VALIDATE_EMAIL) === false) {
            throw new InvalidArgumentException('Le nom et un courriel valide sont requis.');
        }

        return $this->depot->enregistrer([
            'nom' => $nom,
            'courriel' => $courriel,
            'telephone' => $telephone,
        ]);
    }

    public function supprimer(int $id): void
    {
        if ($id <= 0) {
            throw new InvalidArgumentException('L’identifiant doit être positif.');
        }

        $this->depot->supprimer($id);
    }
}

Les vérifications métier restent dans le service : elles ne demandent ni connexion, ni requête HTTP. Les méthodes de lecture transmettent la demande au dépôt, et la suppression refuse un identifiant invalide avant d’appeler la persistance. Le service n’écrit rien dans la réponse HTTP ; le contrôleur se chargera de cette responsabilité dans une leçon ultérieure.

Implémenter le dépôt PDO

Le dépôt reçoit une instance de PDO dans son constructeur. Il satisfait le contrat du projet et concentre les opérations de stockage. Les requêtes ci-dessous sont préparées ; l’étude détaillée des risques d’injection SQL et des paramètres associés est réservée à la leçon de sécurité correspondante.

src/Depot/ContactDepot.phpPHP
<?php

declare(strict_types=1);

namespace Atelier\Depot;

use PDO;

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

    public function lister(): array
    {
        $requete = $this->pdo->query(
            'SELECT id, nom, courriel, telephone FROM contacts ORDER BY nom'
        );

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

    public function rechercher(string $terme): array
    {
        $requete = $this->pdo->prepare(
            'SELECT id, nom, courriel, telephone
             FROM contacts
             WHERE nom LIKE :terme OR courriel LIKE :terme
             ORDER BY nom'
        );
        $requete->execute(['terme' => '%' . $terme . '%']);

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

    public function trouver(int $id): ?array
    {
        $requete = $this->pdo->prepare(
            'SELECT id, nom, courriel, telephone FROM contacts WHERE id = :id'
        );
        $requete->execute(['id' => $id]);
        $contact = $requete->fetch(PDO::FETCH_ASSOC);

        return $contact === false ? null : $contact;
    }

    public function enregistrer(array $donnees): int
    {
        $requete = $this->pdo->prepare(
            'INSERT INTO contacts (nom, courriel, telephone)
             VALUES (:nom, :courriel, :telephone)'
        );
        $requete->execute($donnees);

        return (int) $this->pdo->lastInsertId();
    }

    public function supprimer(int $id): void
    {
        $requete = $this->pdo->prepare('DELETE FROM contacts WHERE id = :id');
        $requete->execute(['id' => $id]);
    }
}

Assembler les objets au démarrage

La composition root est le point unique où l’application construit ses objets et relie leurs dépendances. Dans ce projet, config/services.php joue ce rôle : il lit la configuration, crée PDO, puis fournit le dépôt au service. Les composants reçoivent ainsi leurs dépendances sans connaître les détails de leur construction.

config/services.phpPHP
<?php

declare(strict_types=1);

use Atelier\Depot\ContactDepot;
use Atelier\Service\ContactService;
use PDO;

$dsn = getenv('CARNET_DSN');
$utilisateur = getenv('CARNET_DB_UTILISATEUR');
$motDePasse = getenv('CARNET_DB_MOT_DE_PASSE');

if ($dsn === false || $utilisateur === false || $motDePasse === false) {
    throw new RuntimeException('La configuration de la base de données est incomplète.');
}

$pdo = new PDO($dsn, $utilisateur, $motDePasse, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

$depot = new ContactDepot($pdo);
$contactService = new ContactService($depot);

return [
    PDO::class => $pdo,
    ContactDepot::class => $depot,
    ContactService::class => $contactService,
];

La configuration arrive par variables d’environnement : aucun identifiant de base n’est inscrit dans le code. Après avoir fourni ces variables et chargé l’autoload Composer du projet, l’application peut récupérer le service avec $services = require __DIR__ . '/../config/services.php';, puis utiliser $services[ContactService::class]. Le schéma SQL et les détails de démarrage HTTP seront raccordés dans les leçons consacrées à la réalisation du carnet.

À retenir

  • Injectez les dépendances par constructeur pour rendre leur origine explicite.
  • Faites dépendre le service du contrat de dépôt, et non de PDO.
  • Concentrez les requêtes de persistance dans ContactDepot.
  • Assemblez PDO, le dépôt et le service dans config/services.php.
  • Gardez les paramètres de connexion hors du code source.

La prochaine étape consiste à installer PHPUnit et à configurer le projet pour exécuter automatiquement ses tests.

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.