Внедрение зависимости в хранилище сущностей - PullRequest
26 голосов
/ 16 ноября 2011

Есть ли простой способ внедрить зависимость в каждый экземпляр репозитория в 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;
}

Есть идеи?

Ответы [ 6 ]

35 голосов
/ 16 ноября 2011

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

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

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

<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 и проход компилятора для его обработки и пометить все службы репозитория.

16 голосов
/ 08 июня 2013

Это YAML-версия ответа Aldo, на случай, если вы используете конфигурации 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 - необязательный параметр.Я хочу использовать настройки по умолчанию для своего конкретного использования, поэтому я просто оставил это поле пустым (на случай, если когда-нибудь переименую диспетчер по умолчанию).

10 голосов
/ 16 ноября 2011

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

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

Я ответил, как использовать собственный EntityManager здесь , но я повторю ответ ниже:

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;

Edit

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

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

1 голос
/ 22 июля 2018

Начиная с Symfony 3.3+ и 2017 , вы можете пользоваться услугами.


Вместо других предлагаемых здесь решений:

  • взлом фабрики хранилищ
  • настройка службы в YAML
  • и создание большого количества стандартного кода, который будет выслеживать вас позже

Вы можете сделать это ...


ОчиститьПуть - Зависимость через конструктор инъекций, как и в любом другом сервисе

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


Подробнее в посте

Более подробное руководство вы можете прочитать с примерами понятного кодав Как использовать репозиторий с Doctrine в качестве службы в Symfony post.

1 голос
/ 16 сентября 2015

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

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

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

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

0 голосов
/ 18 мая 2018

На самом деле вы можете создать свой собственный DefaultRepository extends EntityRepository, создать его со всеми необходимыми зависимостями и затем установить его как значение по умолчанию Repository с помощью:

doctrine:
    orm:
        entity_managers:
            default:
                default_repository_class: AppBundle\ORM\DefaultRepository
...