Vous tapez encore $this->getDoctrine()->getRepository() dans vos projets Symfony ? Vous tombez sur des forums qui disent que c’est une pratique dépassée ? Vous voulez juste savoir par quoi remplacer cette ligne de code pour être à jour ?
Cet article va droit au but. On vous montre pourquoi cette méthode est déconseillée et comment la remplacer par l’injection de dépendances, la nouvelle approche recommandée. Vous aurez des exemples de code clairs, à copier-coller, pour moderniser votre application.
`getRepository()` : L’Ancienne vs. la Nouvelle Méthode (Tableau Comparatif)
Pour comprendre tout de suite, voici la différence. Pas besoin de longs discours, le code parle de lui-même. On voit ici comment récupérer des produits dans un controller Symfony, avant et maintenant.
| Ancienne méthode (déconseillée) | Nouvelle méthode (recommandée avec l’autowiring) |
|---|---|
|
|
Pourquoi la méthode `getRepository()` est-elle considérée comme une mauvaise pratique ?
Si l’ancienne méthode fonctionne toujours, pourquoi tout le monde dit d’arrêter de l’utiliser ? La raison principale, c’est que cette pratique date d’avant l’arrivée de l’autowiring puissant de Symfony (depuis les versions 3.4/4.0). Elle pose plusieurs problèmes de fond qui compliquent la vie des développeurs sur le long terme.
Le principal souci est que getRepository() utilise un modèle appelé « Service Locator ». C’est un anti-pattern. En gros, votre controller doit activement aller chercher les outils (dépendances) dont il a besoin, au lieu de les recevoir passivement. C’est comme si un chirurgien devait courir dans tout l’hôpital pour trouver son scalpel au lieu de le recevoir sur un plateau.
Concrètement, le Service Locator cache les dépendances. Quand vous lisez la signature d’une méthode (public function list()), rien n’indique qu’elle a besoin du ProductRepository. C’est une information cachée dans le corps de la fonction, ce qui rend le code plus difficile à comprendre et à maintenir.
Cette approche crée plusieurs problèmes :
- Un couplage fort : Votre controller est directement « soudé » au service
doctrine. Si un jour vous voulez changer de système de base de données (ORM), vous devez modifier le code de tous vos controllers. C’est rigide. - Des tests unitaires difficiles : Pour tester votre controller, vous devez vous « moquer » (simuler) de tout l’objet Doctrine. C’est complexe et lourd. Avec la nouvelle méthode, il suffit de donner un faux repository en argument, ce qui rend les tests unitaires beaucoup plus simples à écrire.
- Des dépendances masquées : Comme mentionné, on ne voit pas au premier coup d’œil de quoi le controller a besoin pour fonctionner. C’est un manque de clarté qui se paie cher sur les gros projets.
Même si ça fonctionne, cette méthode est déconseillée, comme l’indique selon la documentation officielle de Symfony. Le but est d’écrire un code plus propre, plus flexible et plus facile à tester.
La solution moderne : Maîtriser l’Injection de Dépendances
La bonne pratique est donc l’injection de dépendances. Le principe est simple : au lieu que votre objet aille chercher ce dont il a besoin, ses besoins lui sont « injectés » ou « donnés » de l’extérieur. C’est le conteneur de services de Symfony qui s’occupe de tout ce travail en coulisses.
Pour que ça marche, il y a deux éléments clés dans un projet Symfony moderne :
- La classe `ServiceEntityRepository` : Quand vous créez une entité et son repository avec la commande
php bin/console make:entity, Symfony fait en sorte que votre nouvelle classe (par exemple `ProductRepository`) hérite de `ServiceEntityRepository`. Cette classe de base transforme automatiquement votre repository en un service disponible dans toute l’application. - L’autowiring de Symfony : C’est la magie. Quand Symfony voit que vous demandez un objet d’un certain type (par exemple,
ProductRepository) dans le constructeur ou les arguments d’une méthode, il cherche un service qui correspond à ce type. Comme votre repository est un service, Symfony le trouve et vous le donne. C’est le puissant conteneur de services de Symfony qui gère cette connexion automatiquement.
Il n’y a donc presque rien à configurer. Il suffit de demander, et Symfony fournit. Voyons comment faire ça dans les deux cas les plus courants : le controller et le service.
Cas 1 : Injecter un Repository dans un Controller
Dans un controller, vous avez deux manières principales d’injecter un repository. Les deux sont valides, mais l’une est plus courante que l’autre.
Injection dans une méthode d’action (le plus courant)
C’est la méthode que vous avez vue dans le tableau comparatif. Vous ajoutez simplement le repository comme argument de votre méthode d’action. C’est simple et efficace, car vous ne chargez le repository que pour les actions qui en ont réellement besoin.
<?php
namespace App\Controller;
use App\Repository\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
class ProductController extends AbstractController
{
public function show(int $id, ProductRepository $productRepository): Response
{
// Le repository est directement disponible ici
$product = $productRepository->find($id);
if (!$product) {
throw $this->createNotFoundException('Le produit n\'existe pas');
}
// ...
return $this->render('product/show.html.twig', ['product' => $product]);
}
}
Injection dans le constructeur
Vous pouvez aussi injecter le repository dans le constructeur du controller. L’avantage est que le repository devient disponible dans toutes les méthodes du controller via $this->productRepository. C’est utile si la majorité de vos actions ont besoin du même repository.
<?php
namespace App\Controller;
use App\Repository\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
class ProductController extends AbstractController
{
private ProductRepository $productRepository;
public function __construct(ProductRepository $productRepository)
{
$this->productRepository = $productRepository;
}
public function list(): Response
{
// On utilise le repository injecté dans le constructeur
$products = $this->productRepository->findAll();
// ...
return $this->render('product/list.html.twig', ['products' => $products]);
}
public function latest(int $count): Response
{
// On peut le réutiliser ici aussi
$latestProducts = $this->productRepository->findLatest($count);
// ...
return $this->render('product/latest.html.twig', ['products' => $latestProducts]);
}
}
Cas 2 : Injecter un Repository dans un Service personnalisé
Le meilleur endroit pour la logique métier complexe n’est pas le controller, mais un service dédié. Un controller doit rester « maigre » : il reçoit une requête, appelle un service, et renvoie une réponse. La vraie logique (calculer un prix, valider un stock, etc.) doit être dans un service.
Pour les services, la méthode standard est toujours l’injection par le constructeur. C’est la manière la plus propre de définir les dépendances d’un service.
Imaginez un service qui gère la logique de vos produits :
<?php
namespace App\Service;
use App\Repository\ProductRepository;
class ProductService
{
private ProductRepository $productRepository;
// Le repository est injecté une seule fois à la création du service
public function __construct(ProductRepository $productRepository)
{
$this->productRepository = $productRepository;
}
public function getFeaturedProducts(int $limit = 3): array
{
// On utilise le repository pour la logique métier
return $this->productRepository->findBy(['isFeatured' => true], ['createdAt' => 'DESC'], $limit);
}
}
Ensuite, dans votre controller, vous n’injectez plus le repository, mais votre nouveau service. C’est encore plus propre :
<?php
namespace App\Controller;
use App\Service\ProductService; // On injecte notre service
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
class HomeController extends AbstractController
{
public function index(ProductService $productService): Response
{
$featuredProducts = $productService->getFeaturedProducts();
return $this->render('home/index.html.twig', [
'products' => $featuredProducts,
]);
}
}
Ce code est beaucoup plus organisé. Le controller ne sait pas comment on récupère les produits ; il demande juste au `ProductService` de le faire.
Quels sont les avantages concrets de cette nouvelle approche ?
Changer ses habitudes prend un peu de temps, mais les bénéfices de l’injection de dépendances sont réels et vous feront gagner du temps sur le long terme. C’est une pratique standard dans le développement logiciel moderne, bien au-delà de Symfony et de l’ORM Doctrine.
Voici un résumé des avantages :
- Un code plus lisible : Les dépendances d’une classe ou d’une méthode sont listées noir sur blanc dans sa signature (son `__construct` ou ses arguments). En un coup d’œil, vous savez exactement de quoi elle a besoin pour fonctionner. Fini les dépendances cachées.
- Une testabilité grandement améliorée : C’est l’avantage le plus important. Pour tester un service, vous pouvez lui passer un « faux » repository (un mock) qui retourne des données contrôlées. Cela isole votre test et le rend rapide et fiable.
- Un meilleur découplage : Vos composants (controllers, services) ne sont plus directement liés à une implémentation concrète comme l’EntityManager de Doctrine. Ils dépendent d’une « abstraction » (le repository). Votre code devient plus flexible et plus facile à faire évoluer.
- Une meilleure maintenabilité : Un code clair, bien découpé et facile à tester est un code plus simple à maintenir et à faire évoluer. Quand un nouveau développeur arrive sur le projet, il comprend beaucoup plus vite comment les différentes parties de l’application interagissent.
FAQ – Questions fréquentes sur `getRepository`
Même avec les explications, quelques questions reviennent souvent. Voici des réponses directes pour lever les derniers doutes.
Est-ce que `->getRepository()` va être totalement supprimé ?
Probablement pas dans un futur proche. Cette méthode est conservée pour assurer la rétrocompatibilité avec les anciens projets Symfony. Cependant, elle est marquée comme « dépréciée », ce qui signifie que vous ne devriez plus l’utiliser dans du nouveau code. C’est un signal fort de la part de l’équipe de Symfony.
Puis-je encore utiliser `getRepository` dans un vieux projet Symfony ?
Oui, si vous travaillez sur un projet Symfony 3 ou plus ancien, votre code fonctionnera toujours. Mais si vous avez l’occasion de le moderniser (refactoring), le passage à l’injection de dépendances est l’une des premières choses à faire. Cela améliorera immédiatement la qualité et la testabilité de votre code.
Comment injecter plusieurs repositories dans un seul service ?
C’est très simple. Il suffit de les ajouter les uns après les autres comme arguments du constructeur. L’autowiring de Symfony s’occupera de trouver et d’injecter les bonnes instances pour chaque type demandé.
<?php
namespace App\Service;
use App\Repository\ProductRepository;
use App\Repository\UserRepository;
use App\Repository\OrderRepository;
class DashboardService
{
public function __construct(
private ProductRepository $productRepository,
private UserRepository $userRepository,
private OrderRepository $orderRepository
) {
// En PHP 8+, plus besoin de définir les propriétés
// et de les assigner manuellement !
}
public function getDashboardStats(): array
{
$totalProducts = $this->productRepository->count([]);
$totalUsers = $this->userRepository->count([]);
$totalOrders = $this->orderRepository->count([]);
return [
'products' => $totalProducts,
'users' => $totalUsers,
'orders' => $totalOrders,
];
}
}
Quelle est la différence entre `ManagerRegistry`, `EntityManager` et `EntityManagerInterface` ?
C’est une source de confusion fréquente. Voici une explication simple :
ManagerRegistry(accessible via$this->getDoctrine()) : C’est le « chef d’orchestre ». Il connaît toutes les connexions à la base de données et tous les Entity Managers de votre application. C’est un service global qui permet d’accéder à tout ce qui touche à Doctrine.EntityManager: C’est le gestionnaire pour une seule connexion à la base de données. C’est lui qui fait le travail concret : enregistrer des entités (`persist`), les supprimer (`remove`), etc.EntityManagerInterface: C’est une « interface », un contrat. Quand vous demandez unEntityManagerInterface, Symfony vous donne l’EntityManagerconfiguré par défaut. C’est la bonne pratique de demander l’interface plutôt que la classe concrète pour garder un code découplé.
Que faire si l’autowiring ne fonctionne pas pour mon repository ?
Si Symfony vous renvoie une erreur disant qu’il ne peut pas « autowirer » votre repository, vérifiez ces points dans l’ordre :
- Héritage : Votre `ProductRepository` doit bien étendre la classe
Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository. C’est ce qui le déclare comme un service. - Configuration : Dans votre fichier
config/services.yaml, assurez-vous que l’autowiring est bien activé pour votre dossierApp\. Par défaut, c’est le cas :App\: { resource: '../src/', autowire: true, autoconfigure: true }. - Namespace : Vérifiez que le
use App\Repository\ProductRepository;en haut de votre fichier est correct et qu’il n’y a pas de faute de frappe dans le nom de la classe. - Cache : Dans le doute, un petit
php bin/console cache:clearpeut parfois résoudre des problèmes de configuration cachés.
