Подтвердить что ты не робот

Инъекционная зависимость в репозитории сущностей

Есть ли простой способ вставить зависимость в каждый экземпляр репозитория в Doctrine2?

Я попытался прослушивать событие loadClassMetadata и использовать инъекцию установщика в репозитории, но это, естественно, привело к бесконечному циклу, поскольку вызов getRepository в событии вызвал одно и то же событие.

После просмотра метода Doctrine\ORM\EntityManager::getRepository кажется, что репозитории вообще не используют инъекцию зависимостей, вместо этого они создаются на уровне функции:

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

Любые идеи?

4b9b3361

Ответ 1

Если вы используете пользовательский EntityManager, вы можете переопределить метод getRepository. Поскольку это не связано с событием loadClassMetadata, вы не столкнетесь с бесконечным циклом.

Сначала вам нужно передать зависимость своему пользовательскому EntityManager, а затем передать его объекту репозитория с помощью установки setter.

Я ответил, как использовать пользовательский EntityManager here, но я реплицирую ответ ниже:

1 - переопределите параметр doctrine.orm.entity_manager.class, чтобы указать на свой менеджер сущностей (который должен расширять Doctrine\ORM\EntityManager.)

2 - Пользовательский менеджер сущностей должен переопределить метод create, чтобы он возвращал экземпляр вашего класса. Посмотрите мой пример ниже и обратите внимание на последнюю строку в отношении 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());
    }

Вам также понадобится use следующее в вашем классе:

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\ORMException;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Connection;

Изменить

Так как диспетчер объектов по умолчанию создан из метода create, вы не можете просто ввести в него службу. Но поскольку вы создаете настраиваемый диспетчер сущностей, вы можете подключить его к контейнеру службы и ввести все необходимые зависимости.

Затем изнутри переопределенного метода getRepository вы можете сделать что-то вроде
$repository->setFoo($this->foo). Это очень простой пример - вы можете сначала проверить, есть ли $repository метод setFoo перед его вызовом. Реализация зависит от вас, но это показывает, как использовать инъекцию установщика для репозитория.

Ответ 2

Проблема в том, что классы репозитория не являются частью кодовой базы Symfony2, поскольку они являются частью Doctrine2, поэтому они не используют DIC; поэтому вы не можете пойти на инъекцию в одном месте для всех репозиториев.

Я бы посоветовал вам использовать другой подход. Например, вы можете создать сервисный слой поверх репозиториев и фактически ввести класс, который вы хотите, через factory в этом слое.

В противном случае вы также можете определить репозитории как службы следующим образом:

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

Решение, которое может централизовать вызов метода set, заключается в написании тега DIC и прохода компилятора для его обработки и пометки всех служб репозитория.

Ответ 3

Это версия ответа Aldo на YAML, на случай, если вы используете конфигурации YAML вместо 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"]]

И до версии 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]]

Кроме того, в качестве примечания, entity_manager_name является необязательным параметром. Я хочу по умолчанию для своего использования, поэтому я просто оставил его пустым (на всякий случай, когда я когда-либо переименовал менеджер по умолчанию).

Ответ 4

Я только что определил свой собственный класс RepositoryFactory

  • Создайте класс RepositoryFactory и определите службу, например my_service.orm_repository.robo_repository_factory, включите внедрение @service_container
  • И добавьте проверку и установку службы контейнера, например:

    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;
    }
    
  • Создать класс компилятора

    public function process(ContainerBuilder $container)
    {
        $def = $container->getDefinition('doctrine.orm.configuration');
        $def->addMethodCall(
            'setRepositoryFactory', [new Reference('robo_doctrine.orm_repository.robo_repository_factory')]
        );
    }
    
  • После этого любой EntityRepository с ContainerAwareInterface имеет @service_container