如何为单独的捆绑包定义可选依赖项


How can I define Optional dependecies for seperate bundles?

我将用一个例子来解释:有 2 个捆绑包:Foo'SecurityBundleFoo'MenuBundle 包。
Foo'MenuBundle具有如下所示的 Menu 类:

namespace Foo'MenuBundle;
use Foo'SecurityBundle'MenuSecurer; //note this
class Menu{
    protected $securer;
    public function __construct(MenuSecurer $securer = null){
        $this->securer = $securer;
    }
    public function buildMenu(){
        //build the $menu ...
        //...
        if($this->securer != null)
            $securer->secure($menu);
    }
}

如果安装了安全捆绑包,它将自动注入$menuSecurer,但是问题是,当安全捆绑包未安装时,它的类也没有定义,因此即使我并不真正使用它,我也无法在 MenuBundle 中use Foo'SecurityBundle...。解决此问题的正确方法是什么?

symfony 文档中有一节处理这种情况:http://symfony.com/doc/current/book/service_container.html#optional-dependencies-setter-injection

如果你对一个类有可选的依赖项,那么"二传手注入" 可能是更好的选择。

根据这一点,您的类可能如下所示:

namespace Foo'MenuBundle;
class Menu{
    protected $securer;
    public function setSecurer($securer) {
        $this->securer = $securer;
    }
    public function buildMenu(){
        //build the $menu ...
        //...
        if($this->securer != null)
            $securer->secure($menu);
    }
}
# config.yml
menu_service:
    class: Foo'MenuBundle'Menu
    calls:
        - [setMailer, ["@securer"]]

像这样的东西...不幸的是,如果没有您知道存在的接口,您仍然无法拥有 use 语句。

有很多方法可以解决这个问题,但我认为一个好方法是为要注入构造函数第一个参数的类/服务添加一个配置设置。

例如,在 Foo'MenuBundle'Menu 类中(假设这已经定义为服务),您可以在捆绑包的配置中添加一个附加项,以定义$securer的默认服务,然后根据需要在配置中选择性地覆盖它。

在配置类 ( DependecyInjection'Configuration.php 中:

public function getConfigTreeBuilder()
{
    $treeBuilder = new TreeBuilder();
    $rootNode = $treeBuilder->root('foo_menu');
    $rootNode
        ->children()
            ->arrayNode('service')
                ->addDefaultsIfNotSet()
                ->children()
                    ->scalarNode('menu_securer')->defaultValue('foo_security.menu_securer')->end()
               >end()
            ->end()
        ->end();
    return $treeBuilder;
}

在扩展类 ( DependecyInjection'FooMenuExtension.php ):

use Symfony'Component'DependencyInjection'Reference;
use Symfony'Component'DependencyInjection'ContainerBuilder;
use Symfony'Component'Config'FileLocator;
use Symfony'Component'HttpKernel'DependencyInjection'Extension;
use Symfony'Component'DependencyInjection'Loader;
// ...
public function load(array $configs, ContainerBuilder $container)
{
    $configuration = new Configuration();
    $config = $this->processConfiguration($configuration, $configs);
    $loader = new Loader'XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
    // This is what sets 'foo_menu.menu_securer' to the service you want
    foreach ($config['service'] as $key => $service) {
        $container->setAlias($this->getAlias() . '.' . $key, $service);
    }
    $loader->load('services.xml');
    $container->getDefinition('foo_menu.menu.menu')
        ->replaceArgument(0, new Reference('foo_menu.menu_securer'));
}

您的服务定义将如下所示...

<service id="foo_menu.menu.menu" class="%foo_menu.menu.menu.class%">
  <argument /> <!-- foo_menu.menu_securer -->
</service>

现在,在您的config.yml中,您可以通过在...

foo_menu:
    service:
        menu_securer: 'some_other.service'

编辑:关于类型提示,正如Markus所提到的,实现$securer必须实现的接口可能是一个好主意。