【问题标题】:Defining Symfony2 services in a package which isn't a bundle在不是捆绑包的包中定义 Symfony2 服务
【发布时间】:2014-09-11 11:23:40
【问题描述】:

我正在为一个 Symfony 应用程序构建一个包,该应用程序使用了我编写的几个不同的组件。我将调用捆绑包 MyAppBundle。

我的包使用的组件本身并不依赖于 Symfony,但我希望能够为每个组件定义一组 Symfony 服务,这些服务将在每个单独包的 services.yml 文件中定义 -但我不希望这些包成为完全成熟的捆绑包。

为什么?因为我不希望包依赖于 Symfony 框架,所以我不想将包构建为 Symfony 包,并且我希望能够通过注册单个 MyAppBundle 来为我的应用程序注册所有必需的服务,同时如果我愿意,我可以灵活地在不相关的 Symfony 应用程序中使用这些组件的服务。如果我将它们制作成完整的包,那么我必须将每个包单独加载到我的 AppKernel 中,这是我不想要的。

这应该很容易 - 根据我只需要构建容器扩展的文档:http://symfony.com/doc/current/book/service_container.html#importing-configuration-via-container-extensions

所以我为我的组件创建了一个容器扩展来加载它的服务定义:

namespace MyComponent;

class MyComponentExtension extends \Symfony\Component\DependencyInjection\Extension\Extension
{

    public function load(array $configs, ContainerBuilder $container)
    {
        $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
        $loader->load('services.yml');
    }

}

我只是不确定如何从 MyAppBundle 加载此扩展程序?关于如何从另一个包加载扩展的信息似乎很少。也许这不是推荐的做事方式,但由于上述原因,我认为我的用例是合理的。

到目前为止,我从 MyAppBundle 加载 MyComponentExtension 的最佳尝试是:

namespace MyAppBundle;

class MyAppBundle extends Bundle
{

    public function build(ContainerBuilder $container)
    {
        $extensions = array(
            new MyComponentExtension(),
            new MyOtherComponentExtension()
        );
        $aliases = array();
        foreach($extensions as $extension) {
            $container->registerExtension($extension);
            $aliases[] = $extension->getAlias();
        }
        $container->addCompilerPass(new MergeExtensionConfigurationPass($aliases));
    }

}

“addCompilerPass”位是从我能找到的少量信息中收集到的,我相当确定它是错误的。当我这样做时,我无法访问 MyComponentExtension 提供的服务。我很确定我只需要确保将定义正确编译到容器中,但不确定如何执行此操作!

【问题讨论】:

    标签: php symfony


    【解决方案1】:

    有两种类型的耦合:硬耦合和软耦合。 然后你就有了解耦。

    解耦

    当您将组件与捆绑包分离时,您要么让每个部分依赖于一个公共接口 (http://en.wikipedia.org/wiki/Dependency_inversion_principle)。

    或者你可以有一个单向的依赖。在这种情况下,bundle 将依赖于组件。一个例子是依赖于 Symfony 组件的 Symfony 框架包。

    对于第一个示例,您将使用配置来定义要使用的类。 然后包含第二个,您将在包中为组件的类定义服务。同样,这就是 Symfony Framework Bundle 所做的。

    软耦合

    这是当一个库引用另一个(或另一个抽象),但不需要它(除非您尝试使用它,之后它间接成为硬依赖)。例如,如果缺少提供它的资源,则会省略附加功能。

    这方面的一个例子是将你的依赖注入放在组件中,提供对 Symfony 的支持,但不需要它。

    例如,看看Symfony\Bridge\Doctrine\DependencyInjection\AbstractDoctrineExtension

    常见的策略是创建一个抽象扩展,然后在包中覆盖它。 如果需要进一步集成 (http://en.wikipedia.org/wiki/Liskov_substitution_principle),捆绑包可以覆盖组件的某些部分。

    不管怎样

    理想的情况是组件可以单独使用 (http://en.wikipedia.org/wiki/Single_responsibility_principle),并且只使用 Symfony 将它们引导为服务。

    解耦

    Bundle => depends on => Component
    Component depends on nothing
    

    软依赖

    Bundle => depends on => Component  
    Component -> supports -> Symfony
    

    你不应该做的是创建一个双向依赖。那就是每个部分都静态地相互依赖。

    编辑以回应如何:

    你可以,虽然你为什么需要对我来说仍然是个谜。

    class MyAppBundle extends Bundle
    {
        /**
         * Override extension getter.
         */
        public function getContainerExtension()
        {
            if(null === $this->extension) {
                $this->extension = new My\Component\Extension();
            }
    
            return $this->extension;
        }
    }
    

    【讨论】:

    • 感谢您提供的信息丰富的回答。事实上,我希望 MyComponentExtension 仅对 Symfony 有软依赖,但你的回答并没有告诉我如何从 MyAppBundle 正确注册我的 ComponentExtension!
    • 在您给出的 AbstractDoctrineExtension 示例中,您仍然必须有一个必须从 AppKernel 类单独加载的 DoctrineBundle - 这是我想要避免的情况,因为 MyAppBundle 应该加载MyComponentExtension 本身。
    • 是的,但是 Doctrine\Bundle\DoctrineBundle\DependencyInjection\DoctrineExtension 在桥接器中扩展了 AbstractDoctrineExtension
    • 我明白,但是DoctrineExtension是DoctrineBundle的一部分,只能通过注册DoctrineBundle来加载。我的问题是 - 我可以在我的应用程序中注册一个扩展程序而不将该扩展程序作为捆绑包的一部分吗?
    • 已更新以显示如何完成。我之前的回答是试图让你回到忠实的道路:P
    【解决方案2】:

    您可能使这变得比需要的更难。您的 MyAppExtension 可以轻松地从组件库中加载 services.yml。无需组件扩展。

    class MyAppBundleExtension extends Extension
    {
        public function load(array $configs, ContainerBuilder $container)
        {
            $loader = new Loader\YamlFileLoader($container, 
                new FileLocator('path_to_component_library'));
            $loader->load('services.yml');
        }
    

    获取组件库的路径是唯一的小挑战,但即使是硬编码也不错。

    话虽如此,您确实应该考虑为您的库制作一个桥接包。它真正需要的只是一个 Bundle 类、Extension 类和 services.yml 文件。您的图书馆本身可以保持原样。

    桥是 Symfony 2 框架用来访问 Twig 和 Doctrine 等库的工具。很确定它们也适用于您的图书馆。看看:vendor/symfony/monolog-bundle 为例。

    【讨论】:

    • 这有点类似于我最后选择的方法。通过在我的组件中使用静态加载器方法,我避免了对路径进行硬编码。不制作桥接包的原因是我只想在我的 AppKernel 中加载一个包,而不必单独加载其他依赖包。
    【解决方案3】:

    下面是我最终选择的解决方案。我使用静态 buildContainer 方法在 MyComponent 中创建了一个加载器类:

    <?php
    
    namespace MyComponent;
    
    use Symfony\Component\Config\FileLocator;
    use Symfony\Component\DependencyInjection\ContainerBuilder;
    use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
    
    class MyComponentLoader
    {
    
        /**
         * Loads component services into a container.
         *
         * @param ContainerBuilder $container
         */
        public static function buildContainer(ContainerBuilder $container)
        {
            $loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/Resources/config'));
            $loader->load('services.yml');
        }
    
    }
    

    然后我将服务从 MyAppBundle 中的 MyAppExtension 加载到我的容器中:

    <?php
    
    namespace MyAppBundle\DependencyInjection;
    
    use MyComponent\MyComponentLoader;
    use MyOtherComponent\MyOtherComponentLoader;
    use Symfony\Component\Config\FileLocator;
    use Symfony\Component\DependencyInjection\ContainerBuilder;
    use Symfony\Component\DependencyInjection\Extension\Extension;
    use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
    
    class SmartCloneExtension extends Extension
    {
    
        public function load(array $configs, ContainerBuilder $container)
        {
            MyComponentLoader::buildContainer($container);
            MyOtherComponentLoader::buildContainer($container);
        }
    
    }
    

    【讨论】:

    • 好的,但现在你违反了你自己的要求,即你的组件不依赖于 Symfony 2。看起来有点奇怪。
    • 这只是一个软依赖——MyComponentLoader 类有一个依赖于 Symfony2 容器的 buildContainer 方法,但组件的其余部分可以独立工作。我本可以制作一个“桥梁”包,但出于我的目的,这似乎有点矫枉过正。
    • 当然可以,但是为什么在不需要的时候还要依赖呢?这完全没问题,但我确实想确保您完全不需要任何依赖来使其工作。如果你有一个依赖,那么添加一个单独的包类,这将使它成为一个完整的包,这似乎不是矫枉过正。但同样,只要它对你有用。
    • 我同意对于像开源项目这样的项目,桥接包肯定会更好,但这只是我组织内部的。我想避免的主要事情是必须在 AppKernel 中加载多个包以获取在应用程序中注册的主包的所有依赖项,这就是我在这里实现的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-18
    • 2023-03-31
    • 1970-01-01
    • 2012-08-27
    • 1970-01-01
    • 2015-10-10
    • 2016-02-09
    相关资源
    最近更新 更多