Architecture Hexagonale en PHP : code propre sans douleur

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Votre code est une forteresse, pas des spaghettis

\n\n

Avez-vous déjà dû réécrire un projet de Laravel vers Symfony, ou de MySQL vers PostgreSQL ? Si oui, vous connaissez cette douleur : des contrôleurs saturés de requêtes SQL, des modèles qui connaissent tout des requêtes HTTP, et une logique métier qui meurt dès que vous changez de framework. Ça vous parle ?

\n\n

Bienvenue dans le monde de l'architecture Hexagonale en PHP (aussi appelée Ports & Adapters). Ce n'est pas un simple buzzword à la mode, mais une bouée de sauvetage pour les projets qui veulent vivre longtemps et heureux. Dans cet article, nous allons voir comment construire une application PHP qui n'a pas peur du changement : changement de base de données, passage à un autre cache ou une nouvelle API — tout cela deviendra un simple remplacement d'adaptateur, et non une réécriture de la moitié du code.

\n\n

Qu'est-ce que l'architecture Hexagonale vraiment ?

\n\n

Imaginez que votre application est un noyau (core) qui ne sait pas d'où viennent les données : du web, de la console, d'un bot Telegram ou de tests. Autour de ce noyau se trouvent les ports (interfaces), et à l'extérieur, les adaptateurs (implémentations). L'architecture hexagonale en PHP permet de séparer la logique métier du code d'infrastructure. Vous écrivez les règles une fois et les utilisez partout.

\n\n

Principes clés :

\n
    \n
  • Inversion des dépendances — le code de haut niveau ne dépend pas du code de bas niveau. C'est l'inverse.
  • \n
  • Logique métier isolée — aucun framework ne s'infiltre dans vos Use Cases.
  • \n
  • Testabilité — vous pouvez tester le noyau sans base de données ni HTTP.
  • \n
\n\n

Contrairement à la Clean Architecture de Robert Martin, l'Hexagonale est plus flexible : elle n'impose pas un nombre de couches, elle dit simplement : « À l'intérieur, le domaine ; à l'extérieur, l'infrastructure ; connectez-les via des interfaces ».

\n\n

Structure du projet : du chaos à l'ordre

\n\n

Allons droit au but. Voici une structure de dossiers typique pour l'architecture Hexagonale en PHP :

\n\n
src/\n├── Domain/\n│   ├── Entity/\n│   │   └── Order.php\n│   ├── Repository/\n│   │   └── OrderRepositoryInterface.php\n│   └── Service/\n│       └── OrderService.php\n├── Application/\n│   ├── UseCase/\n│   │   └── CreateOrderUseCase.php\n│   └── Port/\n│       ├── Input/\n│       │   └── CreateOrderInputPort.php\n│       └── Output/\n│           └── OrderOutputPort.php\n├── Infrastructure/\n│   ├── Persistence/\n│   │   └── DoctrineOrderRepository.php\n│   └── Http/\n│       └── OrderController.php\n└── Framework/\n    └── Symfony/\n        └── Kernel.php
\n\n

Vous avez remarqué ? Domain et Application ne connaissent pas l'existence de Doctrine, Symfony ou MySQL. Ils travaillent uniquement avec des interfaces. Les adaptateurs vivent dans Infrastructure et sont connectés via l'injection de dépendances.

\n\n

Étape 1 : Définir le noyau — Domain

\n\n

Commençons par l'entité de commande. Pas d'annotations ORM, pas de setters qui violent les invariants :

\n\n
<?php\n// src/Domain/Entity/Order.php\nnamespace App\\Domain\\Entity;\n\nclass Order\n{\n    public function __construct(\n        private readonly string $id,\n        private readonly string $productName,\n        private float $totalAmount,\n        private string $status = 'pending'\n    ) {}\n\n    public function approve(): void\n    {\n        if ($this->status !== 'pending') {\n            throw new \\DomainException('Seules les commandes en attente peuvent être approuvées.');\n        }\n        $this->status = 'approved';\n    }\n\n    public function getId(): string { return $this->id; }\n    public function getProductName(): string { return $this->productName; }\n    public function getTotalAmount(): float { return $this->totalAmount; }\n    public function getStatus(): string { return $this->status; }\n}
\n\n

Et voici le port (interface) pour le repository. Notez qu'il ne connaît rien de Doctrine ou PDO :

\n\n
<?php\n// src/Domain/Repository/OrderRepositoryInterface.php\nnamespace App\\Domain\\Repository;\n\nuse App\\                

Blogs

Book Recommendations