Évolution de l'Architecture : Du Service au Manager
Au cours du développement, la structure initiale Controller <-> Service <-> Repository a montré ses limites (Services "fourre-tout"). Un refactoring complet a été opéré pour adopter des patterns plus granulaires.
🏗️ Nouveaux Patterns implémentés
- Managers : Responsables exclusifs des opérations CRUD et de l'interaction avec les Repositories. Ils portent la logique de persistance (ex:
validateAndSave). - Mappers : Dédiés à la transformation bidirectionnelle
Entity <-> DTO. Cela isole la logique de présentation de la logique de données. - Traits : Utilisés pour la réutilisabilité horizontale (ex: gestion automatique des
createdAt/updatedAtet factorisation duvalidateAndSave). - Utils : Classes utilitaires pures pour des besoins structurels (génération UUID Base 62, méta-données de pagination).
🧠 Pourquoi ce choix ?
Cette structure réduit drastiquement la dette technique. En séparant les responsabilités, chaque classe devient plus courte, plus lisible et, surtout, beaucoup plus simple à tester unitairement.
📁 Structure du Projet (src/)
Voici l'arborescence complète du répertoire source, issue du refactoring :
src
├── Carrier
│ ├── AbstractCarrier.php
│ └── InternCarrier.php
├── Controller
│ ├── Order
│ ├── Product
│ ├── Shipment
│ └── User
├── DataFixtures
├── Doctrine ← Custom DQL Functions (ex: UNACCENT)
├── Dto
│ ├── OpenApiModel
│ ├── Request
│ │ └── Filter
│ ├── Response
│ ├── Types
│ └── User
├── Entity
│ ├── Order
│ ├── Product
│ └── User
├── Enum
│ └── SortFilter
├── EventListener
├── Exception
├── Factory ← CarrierFactory
├── Interface
├── Manager
│ ├── Product
│ ├── Shipment
│ └── User
├── Mapper
│ ├── Product
│ ├── Request
│ ├── Shipment
│ └── User
├── Repository
│ ├── Order
│ ├── Product
│ └── User
├── Response
├── Serializer
│ └── Normalizer
│ ├── Product
│ └── User
├── Service
│ ├── Order
│ ├── Product
│ └── User
├── Trait
├── Util
├── Validator
│ └── Constraints
│ └── Password
└── Voter
Chaque répertoire a une responsabilité unique. Un Manager/Product/ ne touche jamais à la couche Repository/User/ — le couplage est réduit au strict nécessaire.
Factory des Expéditeurs (Carriers)
Pour gérer les différents transporteurs sans répéter de la logique, j'ai mis en place une Factory.
Fonctionnement
On utilise un champ technical_name en base de données (table carrier) qui est mappé sur un Enum PHP. Cela permet de centraliser la logique de création :
- La table
carriercontient le nom technique (ex:la_poste). - Le backend utilise ce nom pour instancier la bonne logique via la Factory.
- Le principe DRY est respecté car toute la gestion des transporteurs passe par ce point unique.