Drupal 8:使用DI正确注入依赖项

如我所确定,到目前为止,依赖注入(DI)和Symfony服务容器是Drupal 8的重要新开发功能。但是,尽管它们已在Drupal开发社区中得到了更好的理解,但仍然存在一些不足关于如何准确地将服务注入Drupal 8类的说明。

许多示例都谈到了服务,但是大多数仅涉及静态的加载方式:

$service = \Drupal::service('service_name');

这是可以理解的,因为正确的注入方法比较冗长,如果您已经知道,则应该重做。 但是, 现实生活中的静态方法仅应在以下两种情况下使用:

  • .module文件中(在类上下文之外)
  • 在类上下文中那些罕见的情况下,在没有服务容器意识的情况下加载类

除此之外,注入服务是最佳实践,因为它可以确保分离的代码并简化测试。

在Drupal 8中,有一些关于依赖项注入的特性,您将无法仅通过纯粹的Symfony方法来理解。 因此,在本文中,我们将看一些在Drupal 8中适当的构造函数注入的示例。为此,为了覆盖所有基础知识,我们将按复杂性的顺序看三种类型的示例:

  • 将服务注入您自己的另一个服务中
  • 将服务注入非服务类
  • 将服务注入插件类

展望未来,假设您已经知道DI是什么,DI的用途以及服务容器如何支持它。 如果没有,我建议您先阅读本文

服务

将服务注入自己的服务非常容易。 由于您是定义服务的人,因此您要做的就是将其作为参数传递给要注入的服务。 想象以下服务定义:

services:
    demo.demo_service:
        class: Drupal\demo\DemoService
    demo.another_demo_service:
        class: Drupal\demo\AnotherDemoService
        arguments: ['@demo.demo_service']

在这里,我们定义了两个服务,其中第二个服务将第一个服务用作构造函数参数。 因此,我们现在在AnotherDemoService类中要做的就是将其存储为局部变量:

class AnotherDemoService {
  /**
   * @var \Drupal\demo\DemoService
   */
  private $demoService;

  public function __construct(DemoService $demoService) {
    $this->demoService = $demoService;
  }

  // The rest of your methods
}

就是这样。 同样重要的是要提到这种方法与Symfony中的方法完全相同,因此这里没有变化。

非服务类

现在,让我们看一下经常与之交互但不是我们自己的服务的类。 要了解这种注入是如何发生的,您需要了解如何解析类以及如何实例化它们。 但是我们很快就会看到这一点。

控制器

控制器类主要用于将路由路径映射到业务逻辑。 他们应该保持精简,并将繁重的业务逻辑委托给服务。 许多扩展了ControllerBase类,并获得了一些帮助器方法来从容器中检索常用服务。 但是,这些是静态返回的

当创建控制器对象( ControllerResolver::createController )时, ClassResolver用于获取控制器类定义的实例。 解析程序可以识别容器,并且如果容器已有控制器,则返回控制器的实例。 相反,它实例化一个新的并将其返回。

这就是我们进行注入的地方:如果要解析的类实现了ContainerAwareInterface ,则通过在接收整个容器的该类上使用静态create()方法来进行实例化。 而且我们的ControllerBase类还实现了ContainerAwareInterface

因此,让我们看一个示例控制器,它使用这种方法正确地注入服务(而不是静态地请求它们):

/**
 * Defines a controller to list blocks.
 */
class BlockListController extends EntityListController {

  /**
   * The theme handler.
   *
   * @var \Drupal\Core\Extension\ThemeHandlerInterface
   */
  protected $themeHandler;

  /**
   * Constructs the BlockListController.
   *
   * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
   *   The theme handler.
   */
  public function __construct(ThemeHandlerInterface $theme_handler) {
    $this->themeHandler = $theme_handler;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('theme_handler')
    );
  }
}

EntityListController类出于此处的目的没有做任何事情,因此,请想象一下BlockListController直接扩展了ControllerBase类,该类又实现了ContainerInjectionInterface

如前所述,实例化此控制器时,将调用静态create()方法。 其目的是实例化该类并将所需的任何参数传递给类构造函数。 并且由于容器已传递给create() ,因此它可以选择要请求的服务并传递给构造函数。

然后,构造函数只需要接收服务并将其存储在本地即可。 请记住,将整个容器注入到您的类中是不好的做法,并且应该始终将注入的服务限制为所需的服务。 而且,如果您需要太多,那么您可能做错了什么。

我们使用了这个控制器示例来更深入地研究Drupal依赖注入方法,并了解构造函数注入的工作方式。 通过使类容器知道也可以使用setter注入,但是我们不会在这里讨论。 相反,让我们看一下可能与之交互以及应该在其中注入服务的类的其他示例。

形式

表单是需要注入服务的类的另一个很好的例子。 通常,您可以扩展已经实现ContainerInjectionInterfaceFormBaseConfigFormBase类。 在这种情况下,如果您覆盖create()和构造函数方法,则可以注入所需的任何内容。 如果您不想扩展这些类,那么您要做的就是自己实现此接口,并按照上面在控制器上看到的相同步骤进行操作。

作为一个例子,让我们来看看SiteInformationForm延伸的ConfigFormBase ,看看它是如何在注入的顶部服务config.factory其父需求:

class SiteInformationForm extends ConfigFormBase {

  ...
  public function __construct(ConfigFactoryInterface $config_factory, AliasManagerInterface $alias_manager, PathValidatorInterface $path_validator, RequestContext $request_context) {
    parent::__construct($config_factory);

    $this->aliasManager = $alias_manager;
    $this->pathValidator = $path_validator;
    $this->requestContext = $request_context;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('config.factory'),
      $container->get('path.alias_manager'),
      $container->get('path.validator'),
      $container->get('router.request_context')
    );
  }
  
  ...
}

和以前一样,将create()方法用于实例化,该方法将父类所需的服务以及其顶部所需的一些其他服务传递给构造函数。

这几乎是基本的构造函数注入在Drupal 8中的工作方式。 几乎在所有类上下文中都可用,除了少数尚未以这种方式解决实例化部分的对象(例如FieldType插件)。 此外,还有一个重要的子系统,该子系统具有一些差异,但要理解它至关重要:插件。

外挂程式

插件系统是一个非常重要的Drupal 8组件,具有许多功能。 因此,让我们看看依赖注入如何与插件类一起工作。

插件如何处理注入的最重要区别是插件类需要实现的接口: ContainerFactoryPluginInterface 。 原因是插件无法解析,但是由插件管理器管理。 因此,当该管理器需要实例化其插件之一时,它将使用工厂来实例化。 通常,这个工厂是ContainerFactory (或类似的变体)。

因此,如果我们查看ContainerFactory::createInstance() ,我们会看到除了将容器传递给常规的create()方法$plugin_id ,还传递了$configuration$plugin_id$plugin_definition变量(这是三个基本每个插件随附的参数)。

因此,让我们看一下注入服务的此类插件的两个示例。 首先,核心的UserLoginBlock插件( @Block ):

class UserLoginBlock extends BlockBase implements ContainerFactoryPluginInterface {

   ...

  public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteMatchInterface $route_match) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);

    $this->routeMatch = $route_match;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('current_route_match')
    );
  }

  ...
}

如您所见,它实现了ContainerFactoryPluginInterface ,并且create()方法接收到这三个额外的参数。 然后将它们以正确的顺序传递给类构造函数,并从容器中请求并传递服务。 这是将服务注入插件类的最基本但仍常用的示例。

另一个有趣的示例是FileWidget插件( @FieldWidget ):

class FileWidget extends WidgetBase implements ContainerFactoryPluginInterface {

  /**
   * {@inheritdoc}
   */
  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ElementInfoManagerInterface $element_info) {
    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
    $this->elementInfo = $element_info;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($plugin_id, $plugin_definition, $configuration['field_definition'], $configuration['settings'], $configuration['third_party_settings'], $container->get('element_info'));
  }
  ...
}

如您所见, create()方法接收相同的参数,但是类构造函数需要特定于此插件类型的额外参数。 这不是问题。 通常可以在该特定插件的$configuration数组中找到它们,并从那里传递它们。

因此,这些是将服务注入插件类的主要区别。 在create()方法中有一个要实现的接口和一些额外的参数。

结论

正如我们在本文中所看到的,在Drupal 8中有很多方法可以使用我们的服务。有时,我们必须静态地请求它们。 但是,大多数时候我们不应该这样做。 我们已经看到了一些典型的示例,说明了何时以及如何将它们注入到类中。 我们还看到了类需要实现的两个主要接口,以便用容器实例化并准备注入,以及它们之间的区别。

如果您在类上下文中工作,并且不确定如何注入服务,请开始研究该类型的其他类。 如果它们是插件,请检查是否有任何父母实现ContainerFactoryPluginInterface 。 如果不是,请为您的类自己做,并确保构造函数收到期望的结果。 还请检查负责的插件管理器类,并查看其使用的工厂。

在其他情况下,例如使用TypedData类(如FieldType ,请查看核心中的其他示例。 如果您看到其他人正在使用静态加载的服务,则很可能尚未准备好进行注入,因此您必须执行相同的操作。 但请注意,因为这将来可能会改变。

翻译自: https://code.tutsplus.com/tutorials/drupal-8-properly-injecting-dependencies-using-di--cms-26314

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值