Modèle Unit of Work
Vue d’ensemble
Section intitulée « Vue d’ensemble »Le modèle Unit of Work maintient une liste d’objets affectés par une transaction métier et coordonne l’écriture des modifications. Il s’assure que toutes les modifications connexes sont validées ensemble ou annulées en cas d’échec.
Objectif
Section intitulée « Objectif »- Gestion des transactions - Grouper les opérations connexes
- Suivi des modifications - Suivre les entités modifiées
- Opérations par lots - Optimiser les écritures en base de données
- Cohérence - S’assurer de l’intégrité des données
Implémentation
Section intitulée « Implémentation »Interface Unit of Work
Section intitulée « Interface Unit of Work »<?php
declare(strict_types=1);
namespace XoopsModules\MyModule\Infrastructure;
interface UnitOfWorkInterface{ public function begin(): void; public function commit(): void; public function rollback(): void; public function registerNew(object $entity): void; public function registerDirty(object $entity): void; public function registerDeleted(object $entity): void;}Implémentation de base
Section intitulée « Implémentation de base »<?php
declare(strict_types=1);
namespace XoopsModules\MyModule\Infrastructure;
final class UnitOfWork implements UnitOfWorkInterface{ private array $newEntities = []; private array $dirtyEntities = []; private array $deletedEntities = []; private bool $inTransaction = false;
public function __construct( private readonly \XoopsDatabase $db, private readonly EntityMapperRegistry $mappers ) {}
public function begin(): void { if ($this->inTransaction) { throw new \RuntimeException('Transaction already started'); }
$this->db->query('START TRANSACTION'); $this->inTransaction = true; }
public function commit(): void { if (!$this->inTransaction) { throw new \RuntimeException('No transaction in progress'); }
try { $this->insertNew(); $this->updateDirty(); $this->deleteRemoved();
$this->db->query('COMMIT'); $this->clear(); } catch (\Exception $e) { $this->rollback(); throw $e; } }
public function rollback(): void { if ($this->inTransaction) { $this->db->query('ROLLBACK'); $this->clear(); } }
public function registerNew(object $entity): void { $id = spl_object_id($entity); $this->newEntities[$id] = $entity; }
public function registerDirty(object $entity): void { $id = spl_object_id($entity); if (!isset($this->newEntities[$id])) { $this->dirtyEntities[$id] = $entity; } }
public function registerDeleted(object $entity): void { $id = spl_object_id($entity); unset($this->newEntities[$id], $this->dirtyEntities[$id]); $this->deletedEntities[$id] = $entity; }
private function insertNew(): void { foreach ($this->newEntities as $entity) { $mapper = $this->mappers->getMapper($entity::class); $mapper->insert($entity); } }
private function updateDirty(): void { foreach ($this->dirtyEntities as $entity) { $mapper = $this->mappers->getMapper($entity::class); $mapper->update($entity); } }
private function deleteRemoved(): void { foreach ($this->deletedEntities as $entity) { $mapper = $this->mappers->getMapper($entity::class); $mapper->delete($entity); } }
private function clear(): void { $this->newEntities = []; $this->dirtyEntities = []; $this->deletedEntities = []; $this->inTransaction = false; }}Utilisation dans les services
Section intitulée « Utilisation dans les services »final class ArticleService{ public function __construct( private readonly UnitOfWorkInterface $unitOfWork, private readonly ArticleRepository $articles, private readonly CommentRepository $comments ) {}
public function publishWithComments( Article $article, array $comments ): void { $this->unitOfWork->begin();
try { // Marquer l'article comme modifié $article->publish(); $this->unitOfWork->registerDirty($article);
// Ajouter de nouveaux commentaires foreach ($comments as $comment) { $this->unitOfWork->registerNew($comment); }
// Valider toutes les modifications ensemble $this->unitOfWork->commit();
} catch (\Exception $e) { $this->unitOfWork->rollback(); throw $e; } }}Avec intégration du dépôt
Section intitulée « Avec intégration du dépôt »final class ArticleRepository implements ArticleRepositoryInterface{ public function __construct( private readonly UnitOfWorkInterface $unitOfWork, private readonly ArticleMapper $mapper ) {}
public function add(Article $article): void { $this->unitOfWork->registerNew($article); }
public function update(Article $article): void { $this->unitOfWork->registerDirty($article); }
public function remove(Article $article): void { $this->unitOfWork->registerDeleted($article); }
public function findById(ArticleId $id): ?Article { return $this->mapper->findById($id); }}Bonnes pratiques
Section intitulée « Bonnes pratiques »- Transactions courtes - Garder les transactions brèves
- Responsabilité unique - Une unit of work par opération métier
- Limites claires - Définir clairement l’étendue de la transaction
- Gestion d’erreurs - Toujours gérer les scénarios d’annulation
- Éviter l’imbrication - Ne pas imbriquer les units of work
Documentation connexe
Section intitulée « Documentation connexe »- Repository-Layer - Modèle de dépôt
- Service-Layer - Modèle de service
- ../Database/Database-Schema - Opérations sur la base de données
- Domain-Model - Entités de domaine