如何在Symfony2中创建动态工厂

时间:2015-08-13 04:45:41

标签: symfony dependency-injection factory

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

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

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

如果你有任何想法如何解决这个问题,我很乐意阅读。

编辑1:我的情况的一个例子 - 我是一个大系统,我现在正在与搜索引擎集成。我的实体是,例如,Blog,Post,User。我想在搜索引擎存储中索引它们,检查索引状态,搜索它们等。在这个例子中,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 Solution进行了一些调整:

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');

1 个答案:

答案 0 :(得分:1)

修改:根据您上面所说的内容,听起来您只需将SearchIntergrationFactory设置为标准服务,然后在您的代码中抓取它并打电话给你#34; build"像往常一样运行,而不是试图从DI容器中进行。 E.g。

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期间所需的依赖项,或者您可以将所需的所有依赖项传递给工厂并完成它。

两者都有其缺点,前者打破了最小的表面积"服务描述的格言,如果你有很多依赖,后者可能是站不住脚的。

在不了解更多有关设置的情况下,它听起来像是某种东西,某处可能需要重构或重新架构,以便这种情况不会出现,但这不在解决这一特定问题的范围之内。