Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
611 views
in Technique[技术] by (71.8m points)

symfony - How to avoid service container in factory models

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 create differente services, each with own dependencies, based on this props and I'd like to create a factory service, so that the interface is simple.

Let me do an example:

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

With this implementation, I have my services created with all dependencies, and a factory called from my controller.

Do you have other ideas, of comment about this solution? I have service container injected withn factory constructor; is there other way to create services from a factory? is there something wrong with this approach?

Thanks in advance

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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 SymfonyComponentDependencyInjectionServiceLocator;

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 SymfonyComponentDependencyInjectionCompilerCompilerPassInterface;
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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...