web-dev-qa-db-fra.com

Injection de dépendance dans le référentiel d'entité

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 ?

25
Hubert Perron

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.

10
Steven Mercatante

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.

34
Aldo Stracquadanio

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).

16
samanime

Je viens de définir ma propre classe RepositoryFactory

  1. Créez la classe RepositoryFactory et définissez le service, par exemple my_service.orm_repository.robo_repository_factory, avec l'injection include @service_container
  2. Et 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;
    }
    
  3. 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')]
        );
    }
    
  4. Après cela, toute EntityRepository avec ContainerAwareInterface a @service_container

1
Vladimir Pak

Depuis Symfony 3.3+ et 2017 vous pouvez utiliser les services. 


Au lieu d’autres solutions proposées ici, cela conduit à:

  • piratage de la fabrique de référentiel
  • faire la configuration de service en YAML
  • et créer beaucoup de code passe-partout qui vous poursuivra plus tard

Tu peux le faire...


Clean Way - Dépendance via l'injection de constructeur comme dans n'importe quel autre service

<?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
    }
}


Lire la suite dans le post

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.

1
Tomáš Votruba