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

时间:2014-11-15 15:20:39

标签: php symfony dependency-injection namespaces

我将用一个例子来解释: 共有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...。这是正确的方法吗?

2 个答案:

答案 0 :(得分:1)

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

  

如果你有一个类的可选依赖项,那么“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语句。

答案 1 :(得分:1)

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

例如,在Foo\MenuBundle\Menu类中(假设已将其定义为服务),您可以在捆绑包的配置中添加一个附加项,以定义{{1}的默认服务然后,如果需要,可以选择在配置中覆盖它。

在配置类($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

编辑:关于类型提示,正如Markus所提到的,实现foo_menu: service: menu_securer: 'some_other.service' 必须实现的接口可能是个好主意。