PHP Academy · Leçon

Agrégats, dépôts et fabriques

Protégez les invariants avec des agrégats et rendez-les persistants proprement.

Leçon 2 sur 413 étapes

Agrégats, dépôts et fabriques est une leçon PHP Academy gratuite sur CoddyKit. Ceci est la leçon 2 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage PHP Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours PHP Academy comprend 4 leçons au total.

Protéger les invariants

Une fois que vous disposez d'entités et d'objets-valeurs, vous avez besoin de motifs pour maintenir des groupes cohérents et pour les faire persister proprement. Le DDD propose trois motifs tactiques : l'agrégat, qui constitue une frontière de cohérence, le dépôt, qui fournit une abstraction de persistance semblable à une collection, et la fabrique, qui prend en charge les constructions complexes. Cette leçon montre comment ils s'assemblent en PHP.

Qu'est-ce qu'un agrégat

Un agrégat est un groupe d'entités et d'objets-valeurs traité comme une seule unité pour les modifications de données. Une entité est la racine de l'agrégat : c'est le seul membre auquel le code externe peut faire référence. Toutes les modifications passent par la racine, qui fait respecter les invariants de l'agrégat. L'agrégat constitue également la frontière transactionnelle : il est chargé et enregistré de manière atomique.

La racine protège l'ensemble

Les appelants externes ne touchent jamais directement aux membres internes. Pour ajouter une ligne de commande, vous appelez une méthode de la racine, qui valide l'opération et maintient la cohérence, notamment des totaux et des limites. Les invariants restent ainsi regroupés au même endroit.

<?php
final class LineItem {
    public function __construct(
        public readonly string $sku,
        public readonly int $qty,
        public readonly int $unitCents
    ) {}
    public function subtotal(): int { return $this->qty * $this->unitCents; }
}

final class Order { // Aggregate Root
    /** @var LineItem[] */
    private array $items = [];
    public function __construct(public readonly string $id) {}

    public function addItem(string $sku, int $qty, int $unitCents): void {
        if ($qty < 1) { throw new DomainException('qty must be >= 1'); }
        $this->items[] = new LineItem($sku, $qty, $unitCents);
    }
    public function total(): int {
        return array_sum(array_map(fn(LineItem $i) => $i->subtotal(), $this->items));
    }
}
$o = new Order('o1');
$o->addItem('A', 2, 500);
$o->addItem('B', 1, 300);
echo $o->total(), PHP_EOL; // 1300

Concevoir de petits agrégats

Une erreur fréquente consiste à créer des agrégats trop volumineux, par exemple un Order qui possède également tout le graphe du Customer. Voici quelques règles générales :

  • Gardez les agrégats petits ; référencez les autres agrégats par leur identifiant au lieu de conserver l'objet.
  • Une transaction ne devrait modifier qu'un seul agrégat ; coordonnez les opérations entre agrégats au moyen d'événements de domaine.
  • Les invariants qui doivent toujours être respectés définissent la frontière.

Référencer par identifiant

La commande stocke un objet-valeur customerId, et non une entité Customer. Cela resserre la frontière de cohérence et évite de charger d'immenses graphes d'objets. La cohérence entre agrégats devient éventuelle et est gérée par des événements plutôt que par une transaction gigantesque.

<?php
final class CustomerId {
    public function __construct(public readonly string $value) {}
}
final class Order {
    public function __construct(
        public readonly string $id,
        public readonly CustomerId $customerId // reference, not object
    ) {}
}
$order = new Order('o1', new CustomerId('cus_99'));
echo $order->customerId->value, PHP_EOL;

Le contrat du dépôt

Un dépôt donne l'illusion d'une collection en mémoire de racines d'agrégats. Le domaine ne dépend que de l'interface ; l'implémentation, qu'elle repose sur Doctrine, PDO ou la mémoire, appartient à la couche d'infrastructure. Les dépôts manipulent des agrégats entiers, jamais des lignes partielles.

<?php
interface OrderRepository {
    public function ofId(string $id): ?Order;
    public function save(Order $order): void;
    public function nextIdentity(): string;
}

Une implémentation en mémoire

Un dépôt en mémoire est particulièrement précieux pour des tests unitaires rapides, sans base de données. Comme le domaine dépend de l'interface, vous pouvez remplacer librement les implémentations : c'est l'inversion des dépendances en action.

<?php
interface OrderRepository {
    public function ofId(string $id): ?object;
    public function save(object $order): void;
    public function nextIdentity(): string;
}
final class Order { public function __construct(public readonly string $id) {} }

final class InMemoryOrderRepository implements OrderRepository {
    private array $store = [];
    public function ofId(string $id): ?object { return $this->store[$id] ?? null; }
    public function save(object $order): void { $this->store[$order->id] = $order; }
    public function nextIdentity(): string { return 'o_' . bin2hex(random_bytes(4)); }
}
$repo = new InMemoryOrderRepository();
$repo->save(new Order('o1'));
var_dump($repo->ofId('o1') !== null);

Un dépôt n'est pas un DAO

Un dépôt n'est pas un DAO CRUD générique. Il expose des requêtes porteuses de sens métier, comme findOverdueOrders(), et reconstitue des agrégats complets avec leurs invariants intacts. Il masque délibérément les détails de SQL et de l'ORM afin que le domaine reste indépendant de la persistance. Évitez de faire entrer des générateurs de requêtes ou des méthodes génériques comme save($anyEntity) dans le domaine.

Des fabriques pour les créations complexes

Lorsque la construction d'un agrégat implique une véritable logique — génération de l'identité, assemblage d'objets-valeurs et application des invariants lors de la création — déplacez-la dans une fabrique, qu'il s'agisse d'une classe dédiée ou d'un constructeur nommé statique. Le constructeur de l'entité reste ainsi simple et les règles d'une création valide sont centralisées.

<?php
final class Order {
    private function __construct(
        public readonly string $id,
        public readonly string $customerId
    ) {}
    public static function place(string $customerId): self {
        if ($customerId === '') { throw new DomainException('customer required'); }
        return new self('o_' . bin2hex(random_bytes(4)), $customerId);
    }
}
$order = Order::place('cus_1');
echo $order->id, PHP_EOL;

Comment ils collaborent

Le flux typique dans un service applicatif est le suivant :

  • Une fabrique, ou un constructeur nommé, crée un agrégat valide.
  • Les méthodes de la racine de l'agrégat font respecter les invariants pendant son utilisation.
  • Un dépôt assure la persistance, puis reconstitue ultérieurement l'agrégat entier.

Le service applicatif orchestre ces opérations dans une transaction par agrégat et ne dépend que d'interfaces.

Faire respecter un invariant de l'agrégat entier

La véritable valeur de la racine réside dans sa capacité à faire respecter les invariants qui s'étendent à plusieurs membres. Ici, la commande rejette une ligne de commande si elle devait faire dépasser le total d'une limite de crédit — une règle qu'aucun LineItem ne pourrait faire respecter seul. Comme toutes les modifications passent par la racine, cette règle ne peut jamais être contournée.

<?php
final class Order {
    private array $items = [];
    public function __construct(
        public readonly string $id,
        private int $creditLimitCents
    ) {}
    public function addItem(int $cents): void {
        if ($this->total() + $cents > $this->creditLimitCents) {
            throw new DomainException('Exceeds credit limit');
        }
        $this->items[] = $cents;
    }
    public function total(): int { return array_sum($this->items); }
}
$o = new Order('o1', 1000);
$o->addItem(600);
try { $o->addItem(600); } catch (DomainException $e) { echo $e->getMessage(), PHP_EOL; }
echo $o->total(), PHP_EOL; // 600

Vérification rapide

Conception d'un agrégat.

Récapitulatif

Vous avez appris à protéger les invariants et à assurer une persistance propre. Les agrégats forment une frontière de cohérence et une frontière transactionnelle ; ils ne sont modifiés que par l'intermédiaire de leur racine et restent petits en référençant les autres agrégats par leur identifiant. Les dépôts présentent les racines d'agrégats comme une collection derrière une interface du domaine, masquent l'ORM et SQL et permettent d'utiliser des doublures de test en mémoire. Les fabriques centralisent les créations complexes qui font respecter les invariants. Ensemble, ils maintiennent votre modèle de domaine cohérent et indépendant de la persistance.

Gratuit pour commencer

Apprends PHP avec un tuteur IA — gratuit

Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.

Cours
49
Leçons
195

Questions Fréquemment Posées

La leçon « Agrégats, dépôts et fabriques » est-elle gratuite ?

Oui — le texte complet de « Agrégats, dépôts et fabriques » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours PHP Academy, passe à CoddyKit PRO. Le cours PHP Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Agrégats, dépôts et fabriques » ?

Protégez les invariants avec des agrégats et rendez-les persistants proprement. Tu pratiques PHP Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer PHP Academy ?

Aucune expérience préalable n'est requise. PHP Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 4.

Combien de temps prend la leçon « Agrégats, dépôts et fabriques » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon PHP Academy ?

Oui. Chaque leçon PHP Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Éléments constitutifs du DDD : entités et objets-valeurs
  2. Agrégats, dépôts et fabriques
  3. Événements et services de domaine
  4. Contextes délimités et cartographie des contextes
← Retour à PHP Academy