Comment mettre de l'ordre dans un projet PHP : Architecture Hexagonale 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

Quand votre monolithe PHP commence à craquer de toutes parts

\n

Avez-vous déjà été dans une situation où le changement de base de données de MySQL à PostgreSQL se transformait en quête d'une semaine ? Ou quand vous deviez ajouter un nouveau client API, mais le code était tellement imbriqué dans le framework qu'il était plus simple de tout réécrire ? Cela vous parle ?

\n

La plupart des développeurs PHP rencontrent tôt ou tard des « spaghettis » dans les contrôleurs et les modèles. Les frameworks comme Laravel ou Symfony offrent des outils pratiques, mais ils imposent également un couplage fort avec eux-mêmes. Il existe une solution — l'architecture Hexagonale (également appelée « ports et adaptateurs », ou architecture hexagonale).

\n

Dans cet article, nous allons voir comment construire une application en PHP qui ne dépend ni du framework, ni de la base de données, ni des services externes. Vous verrez des exemples de code que vous pouvez appliquer immédiatement dans votre projet. C'est parti !

\n\n

Qu'est-ce que l'architecture Hexagonale et pourquoi en avez-vous besoin ?

\n

Imaginez un hexagone. Au centre se trouve votre logique métier (le domaine). Autour, les « ports » (interfaces) par lesquels le monde extérieur communique avec le noyau. Et à l'extérieur, les « adaptateurs » qui implémentent ces ports : contrôleurs, repositories, clients API.

\n

L'idée principale : le noyau de l'application ne sait rien du monde extérieur. Il n'importe pas Laravel, ne connaît pas PDO et n'appelle pas curl. Toute la dépendance est dirigée vers l'intérieur — les adaptateurs dépendent des ports, et non l'inverse.

\n

Les avantages de cette approche sont évidents :

\n
    \n
  • Testabilité : la logique métier peut être testée sans base de données ni requêtes HTTP.
  • \n
  • Flexibilité : remplacer la base de données, la file d'attente ou le framework revient simplement à écrire un nouvel adaptateur.
  • \n
  • Propreté du code : vous ne mélangez pas « quoi faire » avec « comment le faire ».
  • \n
\n

Cela semble magique ? Regardons le code.

\n\n

Structure du projet : squelette de base

\n

Pour l'exemple, prenons un scénario simple : l'inscription d'un utilisateur. Nous devons sauvegarder l'utilisateur dans la base de données et envoyer un email de bienvenue.

\n

Voici à quoi ressemblera une structure de répertoires typique :

\n
src/\n├── Domain/\n│   ├── Entity/\n│   │   └── User.php\n│   ├── Repository/\n│   │   └── UserRepositoryInterface.php\n│   ├── Service/\n│   │   └── UserRegistrationService.php\n│   └── Event/\n│       └── UserRegisteredEvent.php\n├── Infrastructure/\n│   ├── Persistence/\n│   │   └── DoctrineUserRepository.php\n│   ├── Mail/\n│   │   └── SymfonyMailerAdapter.php\n│   └── Controller/\n│       └── RegisterUserController.php\n└── Application/\n    └── UseCase/\n        └── RegisterUserUseCase.php
\n

Notez que Domain est la logique métier pure. Infrastructure sont les adaptateurs. Application sont les cas d'utilisation (use cases).

\n\n

Étape 1 : Le noyau — domaine et ports

\n

Entité (Entity)

\n

Commençons par le plus simple — l'utilisateur. C'est simplement une classe avec des données, sans dépendances.

\n
<?php\nnamespace App\\Domain\\Entity;\n\nclass User\n{\n    public function __construct(\n        private readonly string $id,\n        private string $name,\n        private string $email\n    ) {}\n\n    public function getId(): string\n    {\n        return $this->id;\n    }\n\n    public function getName(): string\n    {\n        return $this->name;\n    }\n\n    public function getEmail(): string\n    {\n        return $this->email;\n    }\n}
\n\n

Port — interface du repository

\n

Définissons maintenant comment nous allons sauvegarder l'utilisateur. C'est un port — une interface qui sera implémentée par un adaptateur.

\n
<?php\nnamespace App\\Domain\\Repository;\n\nuse App\\Domain\\Entity\\User;\n\ninterface UserRepositoryInterface\n{\n    public function save(User $user): void;\n    public function findByEmail(string $email): ?User;\n}
\n\n

Port — interface du service de messagerie

\n

De même pour l'envoi d'emails :

\n
<?php\nnamespace App\\Domain\\Service;\n\nuse App\\                

Blogs

Book Recommendations