【问题标题】:How to create a dynamic factory in Symfony2如何在 Symfony2 中创建动态工厂
【发布时间】:2015-08-13 04:45:40
【问题描述】:

在大多数情况下,我使用服务并利用服务容器。但是,我的一个类,我们称之为 MyClass,有一些依赖项是根据仅在运行时知道的参数动态创建的。例如,如果参数为'A',则应创建MyClass,其依赖关系为Connection、Logger、Dep1A、Dep2A、Dep3A。如果参数为B,则应为Connection、Logger、Dep1B、Dep2B、Dep3B。

我知道在 Symfony 中不可能使用只有在构建容器后才知道的动态参数来创建服务。通过快速搜索,我发现经典的解决方案是对这 3 个动态依赖项进行 setter 注入。问题是我需要在很多地方使用 MyClass,并且每次创建这 3 个依赖项中的每一个都没有意义。

我会说创建一个工厂:MyClassFactory,它获取一个字符串参数并相应地返回 MyClass,应该是解决方案。然而,在 Symfony 中事情变得复杂了,其中 Logger 和 Connection 是服务,我无法将它们自动注入到 Factory 中,因为由于动态参数,我无法将 Factory 作为服务创建。

如果您知道如何解决这个问题,我很乐意阅读。

Edit1:我的情况的一个例子 - 我有一个大系统,我现在正在与搜索引擎集成。例如,我的实体是博客、帖子、用户。我想在搜索引擎存储中索引它们,检查索引状态,搜索它们等。在这个例子中,MyClass 实际上是 SearchIntegration

interface EntityConfigInterface {
    public function getConfig();
}

class PostConfig implements EntityConfigInterface {
    public function getConfig(){}
}
class UserConfig implements EntityConfigInterface {
    public function getConfig(){}
}

interface EntityIndexInterface {
    public function getRecordsToIndex();
    public function countRecords();
}
class PostIndex implements EntityIndexInterface {
    public function getRecordsToIndex(){}
    public function countRecords(){}
}
class UserIndex implements EntityIndexInterface {
    public function getRecordsToIndex(){}
    public function countRecords(){}
}

class SearchIntegration {
    public function __construct(Connection $connection, Logger $logger, $externalSearchEngine, EntityConfigInterface $config, EntityIndexInterface $index) {

    }

    public function checkStatusIndex($entityName) {
        return $this->index->countRecords() === $this->externalSearchEngine->countRecords($entityName);
    }
}

class SearchIntegrationFactory {
    public static function build($entity) {
        switch ($entity) {
            case 'post':
                return new SearchIntegration(new Connection, new Logger, new ExternalSearchEngine, new PostConfig, new PostIndex);
            case 'user':
                return new SearchIntegration(new Connection, new Logger, new ExternalSearchEngine, new UserConfig, new userIndex);
        }

    }
}

Class SearchIntegrationController {
    public function checkIndexStatusForAllEntities() {
        //HOW TO GET THE INSTANCE OF SearchIntegrationFactory::build('user')?
        $userSearchIntegration = $this->get('search_integration'); 
        $userStatus = $userSearchIntegration->checkStatusIndex();

        //HOW TO GET THE INSTANCE OF SearchIntegrationFactory::build('post')?
        $postSearchIntegration = $this->get('search_integration'); 
        $postStatus = $postSearchIntegration->checkStatusIndex();

        echo $userStatus, $postStatus;
    }
}

Edit2:根据@John Noel 解决方案的最终工厂类进行了一些调整:

Config.yml

search_integration_factory:
    class: SearchIntegrationFactory
    arguments:
           [@service_container]

SearchIntegrationFactory 类:

class SearchIntegrationFactory {

    private $container;

    public function __construct(Container $container) {
        $this->container = $container;
    }

    public function build($entity) {
        $entityConfig = $this->container->get($entity . '_config');
        $entityIndex = $this->container->get($entity . '_index');

        return new SearchIntegration(
            $this->container->get('Connection'),
            $this->container->get('logger'),
            new ExternalSearchEngine,
            $entityConfig,
            $entityIndex
        );
    }
}

然后构建类:

$this->get('search_integration_factory')->build('post');
$this->get('search_integration_factory')->build('user');

【问题讨论】:

    标签: symfony dependency-injection factory


    【解决方案1】:

    编辑:因此,根据您上面所说的,听起来您只需将SearchIntergrationFactory 设置为标准服务,然后在您的代码中获取它并调用您的“构建" 像往常一样运行,而不是尝试从 DI 容器中执行它。例如

    searchintegrationfactory:
        class: SearchIntegrationFactory
        calls:
            - [ setLogger, [ @logger ] ]
    

    然后在你的SearchIntegrationController:

    // note, your factory shouldn't use a static method here
    $userSearchIntegration = $this->get('searchintegrationfactory')->build($entity);
    

    这与 Doctrine 进行实体管理的方式几乎相同,每次你想要一个存储库时,你都这样做:

    $this->get('doctrine')->getManager()->getRepository('Entity:Page');
    

    有什么东西阻止你这样做吗?

    --

    听起来你可能想看看使用 Symfony 服务容器 (documentation) 的工厂功能。

    根据您的描述,您的其他班级只想了解MyClass。在这种情况下,您的容器描述可能类似于:

    myclass.factory:
        class: MyClassFactory
        arguments:
            - @connection
            - @logger
    
    myclass:
        class: MyClass # as per documentation, this isn't actually used
        factory: [ @myclass.factory, "getMyClass" ]
        arguments:
            - %runtime_parameter% # if you can parameterise
            - @service.to.get.runtime_parameter # if you can't
    

    现在这不处理您的其他依赖项(Dep1A 等),这取决于它们将指导您如何解决此问题。您可以将容器作为参数传递给您的工厂,只需 get() 创建 MyClass 期间所需的依赖项,或者您可以将所需的所有依赖项传递给工厂并完成它。

    两者都有其缺点,前者打破了服务描述的“最小表面积”准则,而如果您有很多依赖项,后者可能站不住脚。

    如果不了解更多关于设置的信息,听起来像是某处可能需要重构或重新架构,以便不会出现这种情况,但这超出了解决此特定问题的范围。

    【讨论】:

    • 我明白,但我想我在这里遗漏了一些东西,假设我选择将整个容器传递给工厂。为了让工厂创建 MyClass 它需要运行时参数,我如何将它传递给工厂?我看到你在 config.yml myclass: "%runtime_parameter%" 中提到了,但是,在容器构建后无法创建 runtime_parameter。想象一下,我需要在同一个 http 请求中创建 MyClass 两次,第一次使用“A”,第二次使用“B”。这怎么可能?
    • 所以问题是你的“运行时”参数来自哪里?它来自请求对象、您的服务器环境等吗?无论它来自哪里,您都有一种检索它的方法——检查请求或检查环境——在这种情况下,您构建一个服务来检索该参数并将该服务传递给工厂。这就是我上面答案的@service.to.get.runtime_parameter 部分。如果您可以更具体地了解这个“运行时”参数,那会有所帮助。
    • 我已经编辑了我的问题并添加了一个示例,正如您在该示例中看到的那样,有时我需要在同一个请求中多次实例化 SearchIntegration,所以我需要传递我的实体的参数在运行时到工厂。这就是我所缺少的 - 如何将此参数传递给工厂。如果不清楚,请告诉我。谢谢!
    • 我已经更新了上面的答案以尝试匹配新信息。
    • 太好了,我想我们有一个解决方案。实际上,在 build() 方法中我不能做new PostConfig,因为 PostConfig 有一些依赖项,例如数据库连接,所以我需要将此服务注入到“searchintegrationfactory”服务中。问题是要注入的服务取决于我传递给 build() 的实体参数。但是,正如您提到的,我可以传递整个容器,然后根据实体获取正确的服务。我已根据您的输入使用最终解决方案更新了问题。非常感谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-23
    • 1970-01-01
    • 2016-02-13
    相关资源
    最近更新 更多