Search code examples
symfonyserviceautoloader

Symfony2, autoload service in service


Question is simple but... So we have main service:

class ManagerOne {}

and have several another services we want to use in main service:

class ServiceOne{}
class ServiceTwo{}
class ServiceThree{}
class ServiceFour{}
...

Each named as (in services.yml)

service.one
service.two
service.three
service.four
...

Locations of services is different, not in one folder (but I don't think it's a huge trouble for custom autoloader).

Regarding manual we can inject them via __construct() in main service (ManagerOne) but what if we got 20 such services need to be injected? Or use only that we need. Describe them in services as simple inject? O.o I think it's not good idea so.... Also we can inject container and that's it. BUT! Everywhere people saying that inject container worst solution.

What I want. I need method for ManagerOne service which will load service i need by 'service.name' or 'path' with checker 'service exist'.


Solution

  • You could use service tagging and tag each service you want to use in your ManagerOne class. And either use constructor dependency injection or method injection.

    Example:

    First of all you need a compiler pass to collect your tagged services:

    namespace ...\DependencyInjection\Compiler;
    
    use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
    use Symfony\Component\DependencyInjection\Reference;
    use Symfony\Component\DependencyInjection\ContainerBuilder;
    
    class ExamplePass implements CompilerPassInterface
    {
    
        public function process(ContainerBuilder $container)
        {
            if (!$container->hasDefinition("manager.one")) {
                return;
            }
            $services = array();
            foreach ($container->findTaggedServiceIds('managed_service') as $serviceId => $tag) {
                $alias = isset($tag[0]['alias'])
                    ? $tag[0]['alias']
                    : $serviceId;
    
                // Flip, because we want tag aliases (= type identifiers) as keys
                $services[$alias] = new Reference($serviceId);
            }
            $container->getDefinition('manager.one')->replaceArgument(0, $services);
        }
    }
    

    Then you need to add the compiler pass to your bundle class:

    namespace Example\ExampleBundle;
    
    use Symfony\Component\HttpKernel\Bundle\Bundle;
    use Symfony\Component\DependencyInjection\ContainerBuilder;
    use ...\DependencyInjection\Compiler\ExamplePass;
    
    class ExampleBundle extends Bundle
    {
        public function build(ContainerBuilder $container)
        {
            parent::build($container);
            $container->addCompilerPass(new ExamplePass());
        }
    }
    

    Now you can use your services:

    # services.yml
    manager.one:
        class: ManagerClass
        arguments:
            - [] # will be replaced by our compiler pass
    
    services.one:
        class: ServiceOne
        tags:
            - { name: managed_service, alias: service_one }
    
    services.two:
        class: ServiceTwo
        tags:
            - { name: managed_service, alias: service_two }
    

    But caution if you get your manager, all service classes will be automatically created. If this is a performance drawback for you could pass only the service ids (not the Reference) to your management class. Add the @service_container as second argument and create the service as needed.