使用自定义 Doctrine 2 hydrator 进行依赖注入

Posted

技术标签:

【中文标题】使用自定义 Doctrine 2 hydrator 进行依赖注入【英文标题】:Dependency injection with custom Doctrine 2 hydrator 【发布时间】:2014-08-11 10:15:51 【问题描述】:

我在 Symfony 2 项目中的 Doctrine 2 中设置了一个自定义水合器,但要让它完成它需要的工作,它需要另一个服务。 documentation for custom hydrators 只展示了如何提供 hydrator 类,因此无法注入依赖项。

例如:

$em->getConfiguration()->addCustomHydrationMode('CustomHydrator', 'MyProject\Hydrators\CustomHydrator');

我怀疑 Doctrine 正在初始化 hydrator 本身,因此任何依赖项都需要首先通过其他一些 Doctrine 类。

有没有办法提供自定义的“水合工厂”或类似于 Doctrine 的允许注入额外依赖项的方法?如果没有此功能,自定义水合器似乎相当有限。


答案:感谢 Denis V

我得到这个工作如下。我无法发布实际代码,所以我将一些虚拟占位符放在一起,以便您查看它是如何组合在一起的。

src/Acme/ExampleBundle/resources/config/services.yml

services:
    doctrine.orm.entity_manager.abstract:
        class:          Acme\ExampleBundle\Entity\DoctrineEntityManager
        factory_class:  Acme\ExampleBundle\Entity\DoctrineEntityManager
        factory_method: create
        abstract:       true
        calls:
            - [ setMyDependency, [@acme.my_custom_service]]

src/Acme/ExampleBundle/Entity/DoctrineEntityManager.php

namespace Acme\ExampleBundle\Entity;

use Acme\ExampleBundle\Hydrator\MyHydrator;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManager as BaseEntityManager;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\Query;

class DoctrineEntityManager extends BaseEntityManager

    protected $myDependency;

    /**
     * Note: This must be redefined as Doctrine's own entity manager has its own class name hardcoded in.
     */
    public static function create($conn, Configuration $config, EventManager $eventManager = null)
    
        if (!$config->getMetadataDriverImpl()) 
            throw ORMException::missingMappingDriverImpl();
        

        switch (true) 
            case (is_array($conn)):
                $conn = \Doctrine\DBAL\DriverManager::getConnection(
                    $conn, $config, ($eventManager ?: new EventManager())
                );
                break;

            case ($conn instanceof Connection):
                if ($eventManager !== null && $conn->getEventManager() !== $eventManager) 
                     throw ORMException::mismatchedEventManager();
                
                break;

            default:
                throw new \InvalidArgumentException("Invalid argument: " . $conn);
        

        return new self($conn, $config, $conn->getEventManager());
    

    public function setMyDependency($myCustomService)
    
        $this->myDependency = $myCustomService;
    

    public function newHydrator($hydrationMode)
    
        if ($hydrationMode == 'MyHydrationMode') 
            return new MyHydrator($this, $this->myDependency);
        

        return parent::newHydrator($hydrationMode);
    

src/Acme/ExampleBundle/Hydrator/MyHydrator.php

namespace Acme\ExampleBundle\Hydrator;

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;

class MyHydrator extends ObjectHydrator

    protected $myDependency;

    public __construct(EntityManager $em, $myDependency)
    
        parent::__construct($em);

        $this->myDependency = $myDependency;
    

    protected function hydrateAllData()
    
        /* hydration stuff with my dependency here */
    

【问题讨论】:

【参考方案1】:

尝试在你的 config.yml 中添加它

doctrine:
    orm:
        hydrators:
            CustomHydrator: MyProject\Hydrators\CustomHydrator

更新

由于您无法向 Hydrator 本身注入任何东西,因此您可以创建一个自定义 EntityManager(您自己建议)。

可以这样:

services:  
    name_of_your_custom_manager:
        class: %doctrine.orm.entity_manager.class%
        factory_service:  doctrine
        factory_method:   getManager
        arguments: ["name_of_your_custom_manager"]
        calls:
            - [ setCustomDependency, ["@acme_bundle.custom_dependency"] ]

【讨论】:

这仍然让 Doctrine 来实例化 hydrator,这意味着我无法传递其他依赖项。 澄清一下,你想将依赖注入到 Hydrator 本身,而不是实体管理器,我说得对吗? 没错。我认为做到这一点的唯一方法可能是将其注入自定义实体管理器,然后可以将其注入水合器。 是的,这就是我要添加到我的问题中的内容。 EntityManager 在 Hydrator 中可用,因此您可以访问任何注入的依赖项。 啊哈,这看起来可行,谢谢!我没有意识到实体经理有工厂服务。我会在这里尝试并更新。【参考方案2】:

非常好的答案,但请注意,Doctrine 维护者明确表示 not to extend Doctrine\ORM\EntityManager,我想他们将来会最终强制执行。

因此,这里没有违反规则的建议解决方案是更清洁的解决方案:

<?php

declare(strict_types=1);

namespace App\Doctrine\ORM;

use Doctrine\Common\EventManager;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\Decorator\EntityManagerDecorator;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\ORMException;

class EntityManager extends EntityManagerDecorator

    public function __construct(EntityManagerInterface $wrapped)
    
        parent::__construct($wrapped);
    

    public static function create($conn, Configuration $config, EventManager $eventManager = null)
    
        if ( ! $config->getMetadataDriverImpl()) 
            throw ORMException::missingMappingDriverImpl();
        

        switch (true) 
            case (is_array($conn)):
                $conn = \Doctrine\DBAL\DriverManager::getConnection(
                    $conn, $config, ($eventManager ?: new EventManager())
                );
                break;

            case ($conn instanceof Connection):
                if ($eventManager !== null && $conn->getEventManager() !== $eventManager) 
                    throw ORMException::mismatchedEventManager();
                
                break;

            default:
                throw new \InvalidArgumentException("Invalid argument: " . $conn);
        

        return new EntityManager($conn, $config, $conn->getEventManager());
    

现在在你的 services.xml 文件中定义这个服务来装饰你想要的实体管理器:

<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">


    <services>
        <defaults autowire="true" autoconfigure="true" public="false" />

        <service
            id="decorated.doctrine.orm.default_entity_manager"
            class="App\Doctrine\ORM\EntityManager"
            decorates="doctrine.orm.default_entity_manager"
        >
            <argument type="service" id="decorated.doctrine.orm.default_entity_manager.inner" />
        </service>

    </services>
</container>

【讨论】:

我没有看到你对这个被请求的装饰器进行了任何注入。 我要指出的是不要扩展基础实体管理器。

以上是关于使用自定义 Doctrine 2 hydrator 进行依赖注入的主要内容,如果未能解决你的问题,请参考以下文章

如何使 Doctrine hydrator 填充实体?

Doctrine DBAL ->execute() 和 Hydration,DB2 字段名称包括“#”

Doctrine 2 自定义 ObjectMultiCheckbox 值

什么是 Doctrine 水合作用? [关闭]

Doctrine / Symfony:在使用 QueryBuilder 之前将自定义类型转换为数据库值

如何生成扩展自定义记录类的 Doctrine 模型/类