简体   繁体   中英

How can I inject service with setter injection in my Symfony2 extension?

I defined some configuration in my bundle extension:

public function load(array $configs, ContainerBuilder $container)
{
    $configuration = new Configuration();
    $config = $this->processConfiguration($configuration, $configs);

    $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
    $loader->load('services.yml');

    if (!empty($config['my_config'])) {
        $my_config = $config['my_config'];
        if (is_array($my_config)) {
            $service_definition = $container->getDefinition('my_service');
            foreach ($my_config as $name => $class) {
                $reflection_class = new \ReflectionClass($class);

                if (!$reflection_class->implementsInterface('MyInterface')) {
                    throw new \Exception(
                        "'$class' must implement MyInterface'"
                    );
                }

                $service_definition->addMethodCall('addElement', array($name, $class));
            }
        }
    }
}

And then in MyService I have:

public function addElement($name, $class_name) {
    $this->elements[$name] = new $class_name();
}

All these elements implement MyInterface and all is fine until I have new elements that has some dependencies on other services or parameters. These new classes can be defined as services to benefit from DI. But now I need to rewrite MyExtension and MyService to meet new requirements.

How can I get services from container in setter injection that is invoked this way?

I found a solution. I can just test my $class variable:

  • If it is classname of POPO then just use constructor of it in setter injection as I already do
  • If it is a name of service I can provide service object to my setter injection by wrapping $class in Symfony\\Component\\DependencyInjection\\Reference object.

In the MyExtension I need next line:

$service_definition->addMethodCall('addElement', array($name, new Reference($class)));

and in MyService :

public function addElement($name, $object) {
    if (class_exist($object)) {
        $this->elements[$name] = new $object();
    }
    elseif ($object instanceof MyInterface) {
        $this->elements[$name] = $object;
    } else {
        // throw an exception
    }
}

But if you don't have a lot of POPO you can register all your objects as services and as mentioned by @Marino Di Clemente you can just use tagging for getting all the services that can do the work for you.

In this case you will have to define all objects as services and add appropriate tags in its configs. Then you need get it in extension and pass to the setter injection. Code will be similar as mine but instead of parsing config you will need to get tagged services:

public function load(array $configs, ContainerBuilder $container)
{
    $configuration = new Configuration();
    $config = $this->processConfiguration($configuration, $configs);

    $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
    $loader->load('services.yml');

    $service_definition = $container->getDefinition('my_service');
    $taggedServices = $container->findTaggedServiceIds(
        'my_tag'
    );
    foreach ($taggedServices as $id => $tags) {
        $service_definition->addMethodCall(
            'addElement',
            array(new Reference($id))
        );
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM