Внедрение зависимости в репозиторий сущностей

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

Есть идеи ?


person Hubert Perron    schedule 16.11.2011    source источник


Ответы (6)


Если вы используете собственный 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;

Изменить

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

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

person Steven Mercatante    schedule 16.11.2011
comment
Как я могу изменить службу doctrine.orm.entity_manager.class, чтобы добавить инъекцию установщика? Я просмотрел определение в vendor/symfony/src/Symfony/Bundle/DoctrineBundle/Resources/config/orm.xml, но я несколько запутался в том, как добавить инъекцию, поскольку я использую yml для определений своих сервисов. - person Hubert Perron; 16.11.2011
comment
У меня все настроено правильно, но я застрял в той части, когда вы говорите, что можете подключить его к контейнеру службы и внедрить любые зависимости, которые вам нужны. У меня есть метод setContainer в моем пользовательском EntityManager, и я не знаю, какую конфигурацию выбрать. использовать для его подключения. - person Hubert Perron; 17.11.2011
comment
Я также смущен тем, что последний провод до шага сервисного контейнера. - person tetranz; 26.12.2012
comment
Я тоже не знаю, как подключить его к сервисному контейнеру — все остальное выглядит отлично! Любые дальнейшие советы? - person tanGee; 21.12.2013
comment
Можно ли ввести пользовательские параметры/конфигурацию в этот пользовательский менеджер? (Я разместил вопрос здесь). Я думаю, что у меня также есть проблема, чтобы понять, как подключить его к сервисному контейнеру. - person maphe; 24.12.2013
comment
Doctrine — это отдельная библиотека, не зависящая от Symfony. Гораздо лучше было бы обернуть вызовы репозиториев Doctrine в службу. Затем вы сможете внедрить любые необходимые зависимости в службу. Класс репозитория должен отвечать только за выборку данных из сущности или группы сущностей. Если вам нужно сделать что-то еще помимо получения данных, вы должны сделать это в сервисе. - person Jay Sheth; 13.05.2014

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

person Aldo Stracquadanio    schedule 16.11.2011
comment
Проголосовал за отличный ответ, но я выбрал ответ Arms, потому что мне его легче реализовать. - person Hubert Perron; 16.11.2011
comment
Это фантастический ответ. Я полагался на слегка хакерский метод, который перестал работать в версии 2.3. Я перешел на этот метод, и он отлично работает. Я разместил то же самое в формате YAML ниже только для справки. - person samanime; 08.06.2013

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

person samanime    schedule 07.06.2013
comment
Я случайно понизил голос, и теперь он не позволяет мне это исправить :/. Извини за это. Можете ли вы внести небольшое редактирование, чтобы вместо этого я мог проголосовать? - person Cowlby; 18.10.2013
comment
Не волнуйтесь. Я просто был обеспокоен тем, что у меня была ошибка, которую мне нужно было исправить. =) Сделал небольшой твик. - person samanime; 18.10.2013
comment
На самом деле здесь что-то не так. По крайней мере, отступы не годятся. - person Denis V; 19.10.2013

Начиная с 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.

person Tomas Votruba    schedule 22.07.2018

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

  1. Создайте класс RepositoryFactory и определите сервис, например my_service.orm_repository.robo_repository_factory, с инъекцией include @service_container
  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

person Vladimir Pak    schedule 16.09.2015

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

doctrine:
    orm:
        entity_managers:
            default:
                default_repository_class: AppBundle\ORM\DefaultRepository
person cadavre    schedule 18.05.2018