【问题标题】:Symfony2, autoload service in serviceSymfony2,服务中的自动加载服务
【发布时间】:2013-05-19 07:51:22
【问题描述】:

问题很简单,但是... 所以我们有主要的服务:

class ManagerOne {}

还有几个我们想在主服务中使用的其他服务:

class ServiceOne{}
class ServiceTwo{}
class ServiceThree{}
class ServiceFour{}
...

每个命名为(在 services.yml 中)

service.one
service.two
service.three
service.four
...

服务的位置不同,不在一个文件夹中(但我认为自定义自动加载器不会有很大的麻烦)。

关于手册,我们可以通过主服务(ManagerOne)中的 __construct() 注入它们,但是如果我们有 20 个这样的服务需要注入呢?或者只使用我们需要的。在服务中将它们描述为简单的注入? O.o 我认为这不是一个好主意......我们也可以注入容器,仅此而已。但!到处都有人说注入容器是最糟糕的解决方案。

我想要什么。我需要 ManagerOne 服务的方法,它将通过“service.name”或“路径”加载我需要的服务,并带有检查器“服务存在”。

【问题讨论】:

  • 如果需要连接这20个服务,那就是架构不好的标志。
  • 例如我有一个项目。那有 20 个自定义更新程序。而且我无法将它们组合起来并制作一个 factoryClass。重点是我(在 ManagerOne 中)检查项目,获取它对更新程序的访问权限,然后才开始加载它需要的服务。之后通过ManagerOne,我可以调用更新器方法...最后我们有“独立”更新器(我需要这个)和可以使用它们的管理器类。

标签: symfony service autoloader


【解决方案1】:

您可以使用service tagging 并标记您要在ManagerOne 类中使用的每个服务。并且要么使用构造函数依赖注入,要么使用方法注入。

例子:

首先,您需要一个编译器通行证来收集您的标记服务:

namespace ...\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class ExamplePass implements CompilerPassInterface
{

    public function process(ContainerBuilder $container)
    {
        if (!$container->hasDefinition("manager.one")) {
            return;
        }
        $services = array();
        foreach ($container->findTaggedServiceIds('managed_service') as $serviceId => $tag) {
            $alias = isset($tag[0]['alias'])
                ? $tag[0]['alias']
                : $serviceId;

            // Flip, because we want tag aliases (= type identifiers) as keys
            $services[$alias] = new Reference($serviceId);
        }
        $container->getDefinition('manager.one')->replaceArgument(0, $services);
    }
}

然后你需要将编译器传递添加到你的包类:

namespace Example\ExampleBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use ...\DependencyInjection\Compiler\ExamplePass;

class ExampleBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        parent::build($container);
        $container->addCompilerPass(new ExamplePass());
    }
}

现在您可以使用您的服务了:

# services.yml
manager.one:
    class: ManagerClass
    arguments:
        - [] # will be replaced by our compiler pass

services.one:
    class: ServiceOne
    tags:
        - { name: managed_service, alias: service_one }

services.two:
    class: ServiceTwo
    tags:
        - { name: managed_service, alias: service_two }

但请注意,如果您有经理,所有服务类都将自动创建。如果这对您来说是一个性能缺陷,您可以只将服务 ID(而不是参考)传递给您的管理类。添加@service_container 作为第二个参数并根据需要创建服务。

【讨论】:

  • 你能展示一个简单的例子吗,在手册中(一如既往)有很多例子...... :(
  • +1。从来没有做过,但如果我从不需要它,我想我会那样做
  • thnx,我询问了真实示例,因为在手册中通常会显示代码的某些部分的“单点”并且从未准备好代码\模板。正如有些人看到你提供的链接,如果你会尝试它,它不会起作用,因为在第一部分使用 ***_chain 名称(服务),接下来没有它,这就是为什么有些手册真的没用
【解决方案2】:

自 2017 年以来,Symfony 3.3Symplify\PackageBuilder 这变得更加容易。

感谢这个软件包,您可以:

  • 删除标签
  • 简单的 5 行 CompilerPass 在字符串上使用严格类型

让我们来看看你的例子


假设你有

  • 1 位经理 - UpdateManager 班级
  • 许多更新程序 - 一个实现 UpdaterInterface 的类

1。使用PSR-4 autodiscovery 的服务配置

# app/config/services.yml
services:
    _defaults:
        autowire: true

    App\:
        resource: ../../src/App

2。收集编译器通行证

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symplify\PackageBuilder\Adapter\Symfony\DependencyInjection\DefinitionCollector;

final class CollectorCompilerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $containerBuilder)
    {
        DefinitionCollector::loadCollectorWithType(
            $containerBuilder,
            UpdateManager::class,
            UpdaterInterface::class,
            'addUpdater'
        );
    }
}

它收集UpdaterInterface类型的所有服务 并通过addUpdater() 方法将它们添加到UpdateManager

3。在 Bundle 中注册 Compiler Pass

namespace App;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class UpdaterBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        $container->addCompilerPass(new CollectorCompilerPass);
    }
}

仅此而已!

如何添加新的更新程序?

只需创建实现UpdaterInterface 的类,它将被加载到UpdateManager

  • 没有标记
  • 无需手动注册服务
  • 没有无聊的工作

享受吧!

【讨论】:

    猜你喜欢
    • 2023-03-18
    • 2014-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多