Modèle de domaine
Vue d’ensemble
Section intitulée « Vue d’ensemble »Le modèle de domaine représente les concepts métier, les règles et la logique de votre application. Dans le développement de modules XOOPS, les modèles de domaine encapsulent les entités métier principales et leurs comportements.
Entité vs Objet de valeur
Section intitulée « Entité vs Objet de valeur »Les entités ont une identité et un cycle de vie :
<?php
declare(strict_types=1);
namespace XoopsModules\MyModule\Entity;
use XoopsModules\MyModule\ValueObject\ArticleId;
final class Article{ private bool $isNew = true;
public function __construct( private ArticleId $id, private string $title, private string $content, private int $authorId, private int $categoryId, private ArticleStatus $status, private \DateTimeImmutable $createdAt, private ?\DateTimeImmutable $updatedAt = null ) {}
public static function create( string $title, string $content, int $authorId, int $categoryId ): self { return new self( id: ArticleId::generate(), title: $title, content: $content, authorId: $authorId, categoryId: $categoryId, status: ArticleStatus::DRAFT, createdAt: new \DateTimeImmutable() ); }
public function publish(): void { if ($this->status === ArticleStatus::PUBLISHED) { throw new \DomainException('Article is already published'); }
$this->status = ArticleStatus::PUBLISHED; $this->updatedAt = new \DateTimeImmutable(); }
public function updateContent(string $title, string $content): void { $this->title = $title; $this->content = $content; $this->updatedAt = new \DateTimeImmutable(); }
// Accesseurs... public function getId(): ArticleId { return $this->id; } public function getTitle(): string { return $this->title; } public function getContent(): string { return $this->content; } public function getStatus(): ArticleStatus { return $this->status; } public function isNew(): bool { return $this->isNew; }
public function markAsPersisted(): void { $this->isNew = false; }}Objets de valeur
Section intitulée « Objets de valeur »Les objets de valeur sont immuables et comparés par valeur :
<?php
declare(strict_types=1);
namespace XoopsModules\MyModule\ValueObject;
final class ArticleId{ private function __construct( private readonly string $value ) {}
public static function generate(): self { return new self(\Xmf\Ulid::generate()); }
public static function fromString(string $value): self { if (empty($value)) { throw new \InvalidArgumentException('ArticleId cannot be empty'); } return new self($value); }
public function toString(): string { return $this->value; }
public function equals(self $other): bool { return $this->value === $other->value; }}Agrégats
Section intitulée « Agrégats »Les agrégats sont des grappes d’objets de domaine traités comme une seule unité :
final class Category{ private array $articles = [];
public function __construct( private CategoryId $id, private string $name, private ?CategoryId $parentId = null ) {}
public function addArticle(Article $article): void { if ($article->getCategoryId() !== $this->id->toInt()) { throw new \DomainException('Article does not belong to this category'); } $this->articles[] = $article; }
public function getArticleCount(): int { return count($this->articles); }}Événements de domaine
Section intitulée « Événements de domaine »Capturer les occurrences importantes du domaine :
final class ArticlePublishedEvent{ public function __construct( public readonly ArticleId $articleId, public readonly int $authorId, public readonly \DateTimeImmutable $publishedAt ) {}}Énums pour le statut
Section intitulée « Énums pour le statut »Utilisez les énums PHP 8.2+ pour les valeurs de statut sûres en type :
enum ArticleStatus: string{ case DRAFT = 'draft'; case PENDING_REVIEW = 'pending'; case PUBLISHED = 'published'; case ARCHIVED = 'archived';
public function canTransitionTo(self $newStatus): bool { return match($this) { self::DRAFT => in_array($newStatus, [self::PENDING_REVIEW, self::ARCHIVED]), self::PENDING_REVIEW => in_array($newStatus, [self::DRAFT, self::PUBLISHED]), self::PUBLISHED => $newStatus === self::ARCHIVED, self::ARCHIVED => false, }; }}Invariants
Section intitulée « Invariants »Protéger les règles de domaine dans les entités :
final class Article{ public function setTitle(string $title): void { if (strlen($title) < 5) { throw new \DomainException('Title must be at least 5 characters'); } if (strlen($title) > 255) { throw new \DomainException('Title cannot exceed 255 characters'); } $this->title = $title; }
public function archive(): void { if ($this->status === ArticleStatus::DRAFT) { throw new \DomainException('Cannot archive a draft article'); } $this->status = ArticleStatus::ARCHIVED; }}Bonnes pratiques
Section intitulée « Bonnes pratiques »- Modèle de domaine riche - Mettre le comportement dans les entités, pas les services
- Objets de valeur immuables - Les objets de valeur ne doivent jamais changer
- Méthodes usine - Utilisez les méthodes usine statiques pour la construction complexe
- Clauses de garde - Valider les entrées aux frontières des entités
- Événements de domaine - Capturer les changements d’état importants
- Langage omniprésent - Utilisez la terminologie métier dans le code
Documentation connexe
Section intitulée « Documentation connexe »- Service-Layer - Services d’application
- Repository-Layer - Persistance
- DTO-Pattern - Transfert de données
- Event-System - Événements de domaine