【问题标题】:How can I define Optional dependecies for seperate bundles?如何为单独的捆绑包定义可选依赖项?
【发布时间】:2014-11-15 15:20:39
【问题描述】:

我将举例说明: 有 2 个捆绑包:Foo\SecurityBundleFoo\MenuBundle
Foo\MenuBundle 的菜单类如下所示:

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...。解决这个问题的正确方法是什么?

【问题讨论】:

  • 澄清:问题主要是关于使用语句和使用但可能未声明的接口。
  • 可能没有好的解决方案。您可以确保您的包依赖于包含接口的包,或者您可以将接口完全拆分为一个单独的包并需要那个包。或者我可能没有正确理解真正的问题。
  • @ChadSikorra :是的,这确实是我的问题。我目前正在做的事情:创建两个单独的服务,一个没有可选依赖项,一个没有。然后我使用 Compiler Pass in 更改服务定义依赖包。

标签: php symfony dependency-injection namespaces


【解决方案1】:

symfony 文档中有一个部分处理这种情况: 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 语句。

【讨论】:

    【解决方案2】:

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

    例如,在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 必须实现的接口可能是个好主意。

    【讨论】:

      猜你喜欢
      • 2015-10-04
      • 1970-01-01
      • 1970-01-01
      • 2015-12-03
      • 1970-01-01
      • 2015-03-12
      • 2020-01-07
      • 1970-01-01
      • 2019-04-16
      相关资源
      最近更新 更多