如何在我的 Symfony2 扩展中使用 setter 注入来注入服务?

How can I inject service with setter injection in my Symfony2 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));
            }
        }
    }
}

然后在 MyService 我有:

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

所有这些元素都实现了 MyInterface,一切都很好,直到我有了对其他服务或参数有一定依赖性的新元素。这些新的 类 可以定义为受益于 DI 的服务。但是现在我需要重写 MyExtensionMyService 以满足新的要求。

如何从以这种方式调用的 setter 注入中的容器获取服务?

我找到了解决办法。我可以测试我的 $class 变量:

  • 如果它是 POPO 的类名,那么就像我已经做的那样,在 setter 注入中使用它的构造函数
  • 如果它是服务名称,我可以通过将 $class 包装在 Symfony\Component\DependencyInjection\Reference 对象中来为我的 setter 注入提供服务对象。

MyExtension 我需要下一行:

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

并在 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
    }
}

但是如果您没有很多 POPO,您可以将所有对象注册为服务,正如@Marino Di Clemente 所提到的,您可以只使用标记来获取可以为您完成工作的所有服务。

在这种情况下,您必须将所有对象定义为服务并在其配置中添加适当的标签。然后你需要在扩展中获取它并传递给 setter 注入。代码将与我的类似,但您需要获取标记服务而不是解析配置:

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