Utiliser Claude comme assistant d'architecture dans un projet Symfony legacy
Par Louis-Arnaud Catoire
Mis à jour le

Vous ouvrez Claude Code sur votre projet Symfony. Vous lui demandez de créer un use case. Il vous génère un service avec 15 dépendances, des annotations Doctrine dans le domaine, et un contrôleur de 200 lignes. Exactement ce que vous passez vos journées à corriger en code review. Et sur un projet legacy, cette dette technique accumulée ne fait que s'aggraver si l'IA la reproduit.
Le problème n'est pas Claude. C'est que Claude ne connaît pas votre architecture. Il génère du code Symfony "standard" parce que c'est ce qu'il a vu le plus souvent. Quand il s'agit de reprise de projet Symfony, ce décalage entre le code généré et l'architecture existante est encore plus problématique. Votre archi hexagonale, vos conventions DDD, vos règles sur les repositories Doctrine : il ne les connaît pas tant que vous ne les lui expliquez pas.
La solution s'appelle CLAUDE.md. Un fichier à la racine de votre projet que Claude Code lit automatiquement à chaque session. C'est votre onboarding permanent. Et bien configuré, il transforme Claude en assistant qui respecte votre archi dès la première ligne de code.
Le CLAUDE.md minimal qui change tout
Un CLAUDE.md efficace n'est pas une documentation de 500 lignes. C'est un ensemble de règles courtes, précises, que Claude peut appliquer sans ambiguïté.
Voici la structure qu'on utilise sur nos projets Symfony en archi hexagonale :
# CLAUDE.md
## Architecture
Architecture hexagonale stricte. Chaque bounded context suit cette structure :
src/{Context}/
├── Domain/
│ ├── Model/ # Entités domaine (pas d'annotation Doctrine)
│ ├── Port/ # Interfaces (repositories, services externes)
│ ├── Event/ # Événements domaine
│ └── Exception/ # Exceptions métier
├── Application/
│ └── UseCase/ # Un fichier par use case, une méthode __invoke
└── Infrastructure/
├── Persistence/ # Implémentations Doctrine des ports
├── Http/ # Contrôleurs
└── Mapper/ # Conversion entité Doctrine <-> modèle domaine
## Règles strictes
- Le domaine n'importe JAMAIS de namespace Infrastructure ou Application
- Les use cases n'importent JAMAIS de classe concrète, uniquement des interfaces (ports)
- Les contrôleurs appellent un use case, jamais un repository directement
- Pas de logique métier dans les contrôleurs
- Pas d'annotation/attribut Doctrine dans les modèles domaine
Avec ces dix lignes, Claude arrête de générer des contrôleurs-services et commence à respecter les couches.
Besoin d'accompagnement sur votre projet ?
Parlons-enEncoder les conventions Doctrine
Doctrine est le point de friction numéro un entre Claude et votre archi. Par défaut, Claude traite les entités Doctrine comme des modèles domaine. Il colle des #[ORM\Column] partout et appelle $entityManager->flush() depuis les use cases.
Ajoutez une section dédiée dans votre CLAUDE.md :
## Doctrine
- Les entités Doctrine vivent dans Infrastructure/Persistence/Entity/
- Les entités Doctrine ne contiennent AUCUNE logique métier
- Les repositories implémentent les interfaces définies dans Domain/Port/
- Chaque repository utilise un Mapper pour convertir Entity <-> Model
- Ne jamais appeler EntityManager directement dans un use case
- Utiliser les attributs PHP 8 (#[ORM\Entity]) et non les annotations
- Typer les collections : /** @var Collection<int, Tag> */
### Exemple de repository
class DoctrineProductRepository implements ProductRepositoryInterface
{
public function __construct(private EntityManagerInterface $em) {}
public function save(Product $product): void
{
$entity = ProductMapper::toEntity($product);
$this->em->persist($entity);
$this->em->flush();
}
}
L'exemple concret est important. Claude apprend mieux par l'exemple que par la règle abstraite. Un seul repository bien écrit dans le CLAUDE.md et il reproduit le pattern sur tous les autres.
Les règles DDD qui évitent 80% des erreurs de génération
Le DDD mal appliqué par un assistant IA donne du code pire que pas de DDD du tout. Des Value Objects partout sans raison, des Aggregates qui n'agrègent rien, des Domain Events qui ne servent à personne.
Si vous avez un projet legacy à faire évoluer, un guide de migration structuré aide à définir les étapes avant même de configurer l'IA.
Soyez prescriptif sur ce que vous utilisez vraiment :
## DDD
### Value Objects
Utiliser des Value Objects pour : identifiants (ProductId, OrderId),
argent (Money), email (Email), adresses.
Ne PAS créer de Value Object pour un simple string ou int sans logique.
### Identifiants
Tous les identifiants sont des UUIDv7 wrappés dans un Value Object :
final readonly class ProductId
{
public function __construct(public string $value) {}
}
### Événements domaine
Un événement = quelque chose qui S'EST passé (passé composé).
Nommage : {Entité}{Action} → OrderValidated, InvoiceSent
Pas de logique dans l'événement, uniquement des données immutables.
### Use cases
Un use case = une action = un fichier.
Nommage : verbe + nom → CreateOrder, ValidateInvoice, GetProduct
Toujours une seule méthode publique __invoke().
Avec ces conventions, quand vous demandez à Claude "crée le use case pour annuler une commande", il génère un CancelOrder avec un __invoke, qui prend un OrderId en paramètre, appelle un port, et dispatche un OrderCancelled. Sans que vous ayez à le guider ligne par ligne.
Les patterns de test
Si vous ne dites rien sur les tests, Claude va générer des tests d'intégration avec KernelTestCase, une base de données SQLite, et des fixtures Doctrine. Sur un projet legacy, c'est exactement ce que vous essayez d'éliminer.
## Tests
- Tests domaine : PHPUnit pur, pas de container Symfony, pas de base de données
- Utiliser des Fakes (InMemoryProductRepository) plutôt que des mocks
- Les fakes vivent dans tests/{Context}/Infrastructure/Fake/
- Tests d'intégration uniquement pour les adaptateurs infra (repositories Doctrine)
- Nommage : test{Action}{Scenario} → testThrowsWhenProductNotFound
### Structure des tests
tests/
├── Catalog/
│ ├── Domain/
│ │ └── UseCase/
│ │ └── GetProductTest.php # PHPUnit pur
│ └── Infrastructure/
│ ├── Fake/
│ │ └── InMemoryProductRepository.php
│ └── Persistence/
│ └── DoctrineProductRepositoryTest.php # KernelTestCase
Claude va maintenant générer un InMemoryProductRepository quand vous lui demandez des tests, au lieu de monter toute la stack Symfony.
Les hooks Claude Code pour automatiser les vérifications
Claude Code supporte des hooks qui s'exécutent automatiquement après certaines actions. On les utilise pour vérifier que le code généré respecte l'architecture.
Un hook simple avec Deptrac qui vérifie les dépendances entre couches, en s'appuyant sur le composant Console de Symfony pour l'exécution CLI :
{
"hooks": {
"postTool": [
{
"matcher": "Edit|Write",
"command": "vendor/bin/deptrac --no-interaction --formatter=console 2>&1 | tail -5"
}
]
}
}
À chaque fois que Claude modifie ou crée un fichier, Deptrac vérifie que les règles de dépendances sont respectées. Si un use case importe une classe Doctrine, le hook le signale immédiatement. Claude corrige avant même que vous ayez lu le code.
Combine ça avec PHPStan :
{
"hooks": {
"postTool": [
{
"matcher": "Edit|Write",
"command": "vendor/bin/phpstan analyse --no-progress --error-format=raw 2>&1 | head -20"
}
]
}
}
Claude reçoit le feedback de PHPStan en temps réel et corrige ses propres erreurs de typage. Le cycle "générer → vérifier → corriger" se fait sans intervention humaine. Pour tirer le meilleur parti de cette intégration, consulter les 10 erreurs les plus courantes détectées par PHPStan au niveau max sur Symfony permet d'affiner les règles à encoder dans le CLAUDE.md.
Les sous-fichiers CLAUDE.md par contexte
Sur un projet avec plusieurs bounded contexts, un seul CLAUDE.md à la racine ne suffit pas. Claude Code supporte les fichiers CLAUDE.md dans les sous-répertoires. Chaque contexte peut avoir ses propres règles.
src/
├── Catalog/
│ └── CLAUDE.md # Règles spécifiques au catalogue
├── Order/
│ └── CLAUDE.md # Règles spécifiques aux commandes
└── Billing/
└── CLAUDE.md # Règles spécifiques à la facturation
Le CLAUDE.md du contexte Order :
# Order Context
## Aggregate Root
Order est l'aggregate root. Toute modification passe par Order.
Ne jamais modifier OrderLine directement depuis l'extérieur.
## Statuts
Les transitions de statut suivent cette machine à états :
draft → confirmed → shipped → delivered
draft → cancelled
confirmed → cancelled
Toute autre transition doit lever une InvalidStatusTransitionException.
## Intégrations
- Le contexte Order ne référence jamais directement une entité Catalog
- Utiliser ProductId (Value Object) pour référencer un produit
- Les infos produit nécessaires sont copiées dans OrderLine à la création
Quand Claude travaille dans src/Order/, il charge automatiquement ces règles en plus du CLAUDE.md racine. Il sait que Order est un aggregate root, il connaît la machine à états, il ne va pas créer de relation Doctrine vers Product.
Les erreurs à ne pas faire
Ne pas écrire un roman
Un CLAUDE.md de 1000 lignes est contre-productif. Claude a une fenêtre de contexte limitée. Chaque ligne de CLAUDE.md consomme des tokens qui ne sont plus disponibles pour votre code. Soyez concis. Une règle par ligne. Pas d'explications philosophiques sur pourquoi le DDD c'est bien.
Ne pas oublier les exemples
Les règles abstraites sont ambiguës. "Utilise des Value Objects" peut donner n'importe quoi. Un exemple concret de Value Object tel que vous le voulez dans votre projet élimine toute ambiguïté. Mets un exemple par pattern clé : un use case, un repository, un mapper, un test.
Ne pas figer les prompts système
Le CLAUDE.md évolue avec votre projet. Quand l'équipe adopte une nouvelle convention, elle met à jour le CLAUDE.md dans la même PR. Traitez-le comme du code : il est versionné, reviewé, et maintenu. Les conventions de codage PHP et Symfony constituent un bon point de départ pour définir le socle de règles à encoder.
Ne pas ignorer les cas limites du legacy
Votre projet legacy a des exceptions. Des services qui ne suivent pas l'archi hexagonale parce qu'on n'a pas eu le temps de les migrer. Documentez-les :
## Legacy (ne pas migrer)
Les namespaces suivants sont du code legacy non migré.
Ne pas les refactorer sauf demande explicite :
- App\Service\LegacyPaymentService
- App\Controller\Admin\*
- App\Entity\Legacy\*
Sans ça, Claude va essayer de refactorer votre code legacy en archi hexagonale à chaque fois qu'il le touche. Et vous allez passer plus de temps à annuler ses modifications qu'à avancer.
Le résultat en pratique
Après deux semaines avec un CLAUDE.md bien configuré, le constat est net. Les demandes du type "crée le use case pour archiver un document" génèrent du code qui passe la review du premier coup dans 70% des cas. Pas parfait, mais largement au-dessus du 10% qu'on avait sans instructions.
Les juniors de l'équipe utilisent Claude comme un pair programmer qui connaît l'archi. Ils demandent "comment je dois structurer cette feature ?", Claude leur répond avec la bonne structure de répertoires, les bons fichiers à créer, les bons ports à définir. Le CLAUDE.md fait office de documentation vivante de l'architecture.
Le plus inattendu : le CLAUDE.md est devenu le document d'architecture de référence du projet. Plus à jour que le wiki Confluence. Plus lu que les ADR. Parce qu'il a un utilisateur quotidien qui le met à l'épreuve à chaque session : Claude lui-même. Ce type de mise en place fait partie de notre expertise en intelligence artificielle appliquée aux projets Symfony.
Pour aller plus loin
- Symfony AI dans un projet legacy, intégrer l'IA dans du code existant
- RAG avec Symfony AI et Doctrine, indexer sa base métier pour l'IA
- Modernisation applicative, notre parcours complet pour reprendre un legacy en main
- Migration Symfony vers l'architecture hexagonale, structurer le legacy
- Monter en compétence sur Claude Code, le guide complet : skills, hooks et serveurs MCP
- Serveurs MCP pour développeurs Symfony, connecter Claude Code à sa base de données et ses outils
- Documentation Claude Code, guide officiel et configuration des fichiers mémoire
Vous faites face à un projet legacy ou une migration PHP ?
Notre offre de reprise de projet Symfony vous permet de reprendre le contrôle rapidement, avec un audit honnête et une stabilisation progressive.
Découvrir notre offre de repriseArticles connexes

Serveurs MCP et Claude Code : ce que ça change pour un développeur Symfony
Les serveurs MCP connectent Claude Code à vos bases de données, votre GitHub et vos outils de monitoring. Cas d'usage concrets pour un workflow Symfony.
Lire la suite →
10 skills Claude Code pour une équipe Symfony
Dix skills Claude Code prêts à l'emploi pour standardiser les pratiques d'une équipe Symfony : revue de code, tests, migrations, sécurité, refactoring et plus.
Lire la suite →
IA génératives : forces et faiblesses des outils les plus utilisés
Les IA génératives sont-elles aussi puissantes qu'on le dit ? Tour d'horizon des outils qui transforment le quotidien des développeurs.
Lire la suite →