Existe-t-il un moyen simple d'injecter une dépendance dans chaque instance de référentiel dans Doctrine2?
J'ai essayé d'écouter l'événement loadClassMetadata
et d'utiliser l'injection de setter sur le référentiel, mais cela a naturellement abouti à une boucle infinie car l'appel de getRepository
dans l'événement a déclenché le même événement.
Après avoir jeté un œil à la méthode Doctrine\ORM\EntityManager::getRepository
, il semble que les référentiels n’utilisent pas du tout l’injection de dépendance, mais qu’ils soient instanciés au niveau de la fonction:
public function getRepository($entityName)
{
$entityName = ltrim($entityName, '\\');
if (isset($this->repositories[$entityName])) {
return $this->repositories[$entityName];
}
$metadata = $this->getClassMetadata($entityName);
$customRepositoryClassName = $metadata->customRepositoryClassName;
if ($customRepositoryClassName !== null) {
$repository = new $customRepositoryClassName($this, $metadata);
} else {
$repository = new EntityRepository($this, $metadata);
}
$this->repositories[$entityName] = $repository;
return $repository;
}
Des idées ?
Si vous utilisez un EntityManager personnalisé, vous pouvez remplacer la méthode getRepository
. Etant donné que cela n'implique pas l'événement loadClassMetadata
, vous ne rencontrerez pas de boucle infinie.
Vous devez d'abord transmettre la dépendance à votre EntityManager personnalisé, puis à l'objet de référentiel à l'aide de l'injection de setter.
J'ai expliqué comment utiliser un EntityManager personnalisé ici , mais je vais reproduire la réponse ci-dessous:
1 - Remplacez le paramètre doctrine.orm.entity_manager.class
pour qu'il pointe vers votre gestionnaire d'entités personnalisé (qui devrait étendre Doctrine\ORM\EntityManager
.).
2 - Votre gestionnaire d’entités personnalisé doit remplacer la méthode create
pour qu’il retourne une instance de votre classe. Voir mon exemple ci-dessous et notez la dernière ligne concernant MyEntityManager
:
public static function create($conn, Configuration $config, EventManager $eventManager = null) {
if (!$config->getMetadataDriverImpl()) {
throw ORMException::missingMappingDriverImpl();
}
if (is_array($conn)) {
$conn = \Doctrine\DBAL\DriverManager::getConnection($conn, $config, ($eventManager ? : new EventManager()));
} else if ($conn instanceof Connection) {
if ($eventManager !== null && $conn->getEventManager() !== $eventManager) {
throw ORMException::mismatchedEventManager();
}
} else {
throw new \InvalidArgumentException("Invalid argument: " . $conn);
}
// This is where you return an instance of your custom class!
return new MyEntityManager($conn, $config, $conn->getEventManager());
}
Vous devrez également use
les éléments suivants dans votre classe:
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\ORMException;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Connection;
Modifier
Le gestionnaire d'entités par défaut étant créé à partir de la méthode create
, vous ne pouvez pas simplement y injecter un service. Mais puisque vous créez un gestionnaire d’entités personnalisé, vous pouvez le connecter au conteneur de services et injecter toutes les dépendances dont vous avez besoin.
Ensuite, dans la méthode getRepository
remplacée, vous pouvez faire quelque chose comme:$repository->setFoo($this->foo)
. C'est un exemple très simple - vous voudrez peut-être d'abord vérifier si $repository
possède une méthode setFoo
avant de l'appeler. L'implémentation est à vous, mais cela montre comment utiliser l'injection de setter pour un référentiel.
Le problème est que les classes de référentiel ne font pas partie de la base de code Symfony2, car elles font partie de Doctrine2, de sorte qu'elles ne tirent pas parti du DIC. C'est pourquoi vous ne pouvez pas faire l'injection au même endroit pour tous les dépôts.
Je vous conseillerais d'utiliser une approche différente. Par exemple, vous pouvez créer une couche de service au-dessus des référentiels et injecter la classe souhaitée via une fabrique de cette couche.
Sinon, vous pouvez également définir les référentiels en tant que services de la manière suivante:
<service id="your_namespace.repository.repos_name"
class="%your_namespace.repository.repos_name%"
factory-service="doctrine" factory-method="getRepository">
<argument>entity_name</argument>
<argument>entity_manager_name</argument>
<call method="yourSetter">
<argument>your_argument</argument>
</call>
</service>
Une solution qui pourrait centraliser l'appel de la méthode set consiste à écrire une balise DIC et une passe du compilateur pour la gérer et baliser tous les services de référentiel.
Ceci est une version YAML de la réponse d'Aldo, au cas où vous utiliseriez des configurations YAML au lieu de XML.
your_namespace.repository.repos_name:
class: %your_namespace.repository.repos_name%
factory: ["@doctrine", getRepository]
arguments:
- entity_name
- entity_manager_name
calls:
- [setContainer, ["@service_container"]]
Et avant la version 2.8:
your_namespace.repository.repos_name:
class: %your_namespace.repository.repos_name%
factory_service: doctrine
factory_method: getRepository
arguments:
- entity_name
- entity_manager_name
calls:
- [setContainer, [@service_container]]
De plus, en tant que note, entity_manager_name est un paramètre facultatif. Je veux la valeur par défaut pour mon usage particulier, alors je la laisse vide (juste au cas où je renommerais le gestionnaire par défaut).
Je viens de définir ma propre classe RepositoryFactory
my_service.orm_repository.robo_repository_factory
, avec l'injection include @service_containerEt ajoutez check et set service de conteneur, par exemple:
private function createRepository(EntityManagerInterface $entityManager, $entityName)
{
/* @var $metadata \Doctrine\ORM\Mapping\ClassMetadata */
$metadata = $entityManager->getClassMetadata($entityName);
$repositoryClassName = $metadata->customRepositoryClassName
?: $entityManager->getConfiguration()->getDefaultRepositoryClassName();
$result = new $repositoryClassName($entityManager, $metadata);
if ($result instanceof ContainerAwareInterface) {
$result->setContainer($this->container);
}
return $result;
}
Créer une classe de compilateur
public function process(ContainerBuilder $container)
{
$def = $container->getDefinition('doctrine.orm.configuration');
$def->addMethodCall(
'setRepositoryFactory', [new Reference('robo_doctrine.orm_repository.robo_repository_factory')]
);
}
Après cela, toute EntityRepository
avec ContainerAwareInterface
a @service_container
Depuis Symfony 3.3+ et 2017 vous pouvez utiliser les services.
Au lieu d’autres solutions proposées ici, cela conduit à:
Tu peux le faire...
<?php declare(strict_types=1);
namespace App\Repository;
use App\Entity\Post;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
final class PostRepository
{
/**
* @var EntityRepository
*/
private $repository;
/**
* @var YourOwnDependency
*/
private $yourOwnDependency;
public function __construct(YourOwnDependency $YourOwnDependency, EntityManager $entityManager)
{
$this->repository = $entityManager->getRepository(Post::class);
$this->yourOwnDependency = $yourOwnDependency
}
}
Vous pouvez lire un tutoriel plus détaillé avec des exemples de code clairs dans Comment utiliser Repository avec Doctrine as Service dans Symfony post.