如何避免工厂模型中的服务容器

时间:2018-11-21 12:22:16

标签: symfony dependency-injection

我正在将Symfony 4用于一个项目,并且对工厂有疑问。

假设我有一种策略,具体取决于一种字符串。

我想基于此道具创建不同的服务,每个服务都有自己的依赖关系,并且我想创建工厂服务,以便界面简单。

让我举个例子:

class ServiceBar implements Doing{
    public function __construct($dep1,$dep2){
    }
    public function do();
}

class ServiceBaz implements Doing{
    public function __construct($dep3,$dep4){
    }
    public function do();
}


// Factory Class
class MyServiceFactory{
    protected $services = [
        'bar' => 'app.service.bar',
        'baz' => 'app.service.baz'
    ];

    public function __construct(ContainerInterface $sc){
        $this->sc = $sc;
    }

    public function factory($string){
        if(!$this->sc->has($this->services[$string])){
            throw new Exception("Missing Service");
        }
        $this->sc->get($this->services[$string])->do();        

    }

}

// IndexController.php
public function indexAction(Request $request, MyServiceFactory $factory)
{
    $factory->factory($request->get('action'));
}

通过此实现,我创建了具有所有依赖关系的服务,并从控制器中调用了一个工厂。

对于此解决方案,您还有其他意见吗? 我已经用工厂构造函数注入了服务容器;还有其他方法可以从工厂创建服务吗?这种方法有什么问题吗?

预先感谢

2 个答案:

答案 0 :(得分:1)

可以使用Symfony Service Locator来避免注入整个容器的麻烦。定位器的作用就像一个容器,但只能访问有限数量的服务。

配置所有内容需要一些魔术。就您而言,您只希望定位器访问实现Doing接口的服务。

从定位器开始,该定位器将像所有容器一样继承get / has方法:

use Symfony\Component\DependencyInjection\ServiceLocator;

class DoingLocator extends ServiceLocator
{
    protected $services = [
        'bar' => 'app.service.bar',
        'baz' => 'app.service.baz'
    ];
    public function locate($string) {
        return $this->get($this->services[$string]);
    }
}

魔法来了。实际上,您可以按照文档在services.yaml中手动配置它,但是自动执行会更有趣。

首先使您的内核类成为编译器传递:

# src/Kernel.php
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
class Kernel extends BaseKernel implements CompilerPassInterface

下一步,自动标记实现Doing接口的所有服务:

# Kernel.php
protected function build(ContainerBuilder $container)
{
    $container->registerForAutoconfiguration(Doing::class)
        ->addTag('doing');
}

最后,添加一个编译器通道以构建您的定位器服务:

# Kernel.php
public function process(ContainerBuilder $container)
{
    $doingLocatorIds = [];
    foreach ($container->findTaggedServiceIds('doing') as $id => $tags) {
        $doingLocatorIds[$id] = new Reference($id);
    }
    $doingLocator = $container->getDefinition(DoingLocator::class);
    $doingLocator->setArguments([$doingLocatorIds]);
}

然后保存。大功告成现在,您可以注入您的DoingLocator(又名MyServiceFactory),并且一切都会很好。

答案 1 :(得分:0)

您可以使用自己的编译器通道,扩展和服务定位符。这是Symfony允许的一种方式,但需要大量代码。

自动接线阵列

最简单的方法是通过自动装配数组自动装配参数。

  • 没有容器依赖性
  • 无扩展名
  • 无捆绑注册
  • 1次编译器通过

示例

/**
 * @param Doing[] $doings
 */
public function __construct(array $doings)
{
    $this->doings = $doings;
}

public function create(string $name): Doing
{ 
    foreach ($this->doings as $doing) {
        if ($doing->getName() === name) { // this depends on your design; can be also "is_a" or "instanceof"
            return $doing;
        }
    }

    throw new MissingDoingException;
}

这也称为收集器模式

如何集成