将依赖注入实体存储库

时间:2011-11-16 14:18:01

标签: symfony doctrine-orm

是否有一种简单的方法可以将依赖项注入Doctrine2中的每个存储库实例?

我已经尝试过监听loadClassMetadata事件并在存储库中使用setter注入,但这自然会导致无限循环,因为在事件中调用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 个答案:

答案 0 :(得分:35)

问题是存储库类不是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>

可以集中设置方法调用的解决方案是编写DIC标记和编译器传递来处理它并标记所有存储库服务。

答案 1 :(得分:16)

这是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是可选参数。我希望我的特定用途的默认值,所以我把它留空(以防我重命名默认管理器)。

答案 2 :(得分:10)

如果使用自定义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方法。实现取决于您,但这显示了如何对存储库使用setter注入。

答案 3 :(得分:1)

我只是定义了自己的RepositoryFactory类

  1. 创建RepositoryFactory类并定义服务,例如my_service.orm_repository.robo_repository_factory,包含@service_container injection
  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 {@ 1}}有@service_container

答案 4 :(得分:1)

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


阅读更多帖子

您可以在How to use Repository with Doctrine as Service in Symfony帖子中阅读带有清晰代码示例的更详细的教程。

答案 5 :(得分:-1)

您实际上可以创建自己的DefaultRepository extends EntityRepository,使用您需要的所有依赖项构建它,然后将其设置为默认Repository

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