How to avoid service container in factory models

后端 未结 2 689
夕颜
夕颜 2020-12-11 09:51

I\'m using Symfony 4 for a project, and I have a question regarding factories.

Assume that I have a strategy depending on a kind of string.

I\'d like to crea

相关标签:
2条回答
  • 2020-12-11 10:30

    A Symfony Service Locator can be used to avoid the need to inject the complete container. The locator acts like a container but only has access to a limited number of services.

    It takes a bit of magic to configure everything. In your case, you only want the locator to access services implementing the Doing interface.

    Start with the locator which will inherit get/has methods like all containers:

    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]);
        }
    }
    

    Now comes the magic. You actually could configure this manually in services.yaml per the documentation but it is more fun to do it automatically.

    Start by making your Kernel class a compiler pass:

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

    Next, automatically tag all services that implement the Doing interface:

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

    Finally, add a compiler pass to build your locator service:

    # 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]);
    }
    

    And presto. You are done. You can now inject your DoingLocator (aka MyServiceFactory) and all should be well.

    0 讨论(0)
  • 2020-12-11 10:46

    You can use own compiler passes, extensions and service locators. It's a way Symfony allows that, but it requires lot of code.

    Autowired Array

    The simplest approach is to autowire arguments by autowired array.

    • no container dependency
    • no extension
    • no bundle registration
    • 1 compiler pass

    Example

    /**
     * @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;
    }
    

    This is also called collector pattern.

    How to Integrate

    • You can a post with example about this here
    • Or use the Compiler pass
    0 讨论(0)
提交回复
热议问题