Still to do:
- IndexAddFieldsForm
- IndexStatusForm
- ContentEntity
- ContentAccess
- SearchApiMoreLikeThis
- SearchApiCache
- SearchApiRelationship

Part of this will be covered by #2753763: Add a logging trait which can also log exceptions.


Our plugins currently don't fully utilize dependency injection. Especially, although we purposedly use the t() method for translation, we fail to use dependency injection to get the translation service and thus still use \Drupal::service('string_translation') under the hood. Other services used by individual plugins were also not properly (or at all) injected.

The patter that makes most sense, I think, is this:

  public static function create() {
    $plugin = parent::create();
    $plugin->setService($container->get('service_id'));
    return $plugin;
  }

  public function getService() {
    return $this->service ?: \Drupal::service('service_id');
  }

  public function setService(Service $service) {
    $this->service = $service;
  }

That way, we also have the advantage of only needing the code for injecting the string translation service once.

Already fixed it, just wanted to have a reference for the commit.

Comments

drunken monkey’s picture

Title: Implement proper dependency injection for plugins » Implement proper dependency injection where possible
Category: Task » Bug report
Priority: Normal » Major
Status: Fixed » Active

OK, there are a lot of problems here (not only in plugins, but also forms, and one in the datasource plugin manager), so I didn't manage to fix all of them right now. I'll just do that when I get to the code cleanup of the respective files.

A good rule of thumb is probably that no class which has DI available should contain the string \Drupal::. One thing I still need to figure out, though, is whether that also applies to \Drupal::formBuilder() in plugin forms. Does it make sense to inject the form builder?
Core is useless as a reference here, as it still uses the (deprecated) form_set_error() function, which is definitely wrong.

  • drunken monkey committed 3a85d50 on master
    Issue #2311039 by drunken monkey: Implemented proper dependency...
drunken monkey’s picture

watchdog() calls should probably also be replaced with calls to a dependency-injected logger (factory). watchdog_exception() could be a little trickier, as it also has some logic attached, but could be done as well.

drunken monkey’s picture

Issue summary: View changes

  • drunken monkey committed 3a85d50 on 2349435-seperate-processors-by-stage
    Issue #2311039 by drunken monkey: Implemented proper dependency...
drunken monkey’s picture

Project: Search API (8.x) » Search API
Version: » 8.x-1.x-dev
drunken monkey’s picture

Still to do:
- IndexAddFieldsForm
- IndexStatusForm
- ContentEntity
- ContentAccess
- SearchApiMoreLikeThis
- SearchApiCache
- SearchApiRelationship

(Comes from git grep '\Drupal::' | grep -v 'return \$this->.* ?:' | grep -v '^[^:]*Test').

Part of this will be covered by #2753763: Add a logging trait which can also log exceptions.

joachim’s picture

Why do you need this:

  public function getService() {
    return $this->service ?: \Drupal::service('service_id');
  }

If you've set the service in create() then it's there. At least that's how every other plugin I've worked with so far works. Is SearchAPI doing something different with the plugin manager?

drunken monkey’s picture

Status: Active » Needs review
StatusFileSize
new42.63 KB

It is done!
Does anyone want to review?

drunken monkey’s picture

StatusFileSize
new32.6 KB
new74.44 KB

This also takes care of the overlap with #2776659: Remove calls to deprecated Utility methods – i.e., provides dependency injection for the instances where Utility::x() calls would actually need dependency injection.

But come to think of it, if we don't need create() for dependency injection anyways, there's not really a reason to not provide it on entity classes, too, right? Like, just move the \Drupal::service() calls (and similar) to dedicated getters with accompanying property and setter.
Any opinions on that?

Status: Needs review » Needs work

The last submitted patch, 10: 2311039-10--dependency_injection.patch, failed testing.

joachim’s picture

I seem to remember reading somewhere that the reason entity classes can't have service properties is that the entity object it sometimes serialized.

drunken monkey’s picture

Status: Needs work » Needs review
StatusFileSize
new569 bytes
new74.74 KB

58 fails for one missing use statement?

borisson_’s picture

Status: Needs review » Reviewed & tested by the community

I don't really like the setter-injection and prefer constructor-only injection myself but overall this patch looks great, let's go ahead and commit this.

  • drunken monkey committed 1a29559 on 8.x-1.x
    Issue #2311039 by drunken monkey: Fixed missing dependency injection for...
drunken monkey’s picture

Status: Reviewed & tested by the community » Fixed

Thanks for reviewing, good to hear!
Committed.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

ambient.impact’s picture

This is a bit of a tangent, but this got me thinking:

I don't really like the setter-injection and prefer constructor-only injection myself

While developing some process plug-ins myself, I noticed this difference from core. I've been using constructor-only for so long, and it generally keeps things fairly simple, but sometimes you end up with a really unwieldy mess whenever a sub-class has to inject an additional dependency, like in this example from a plug-in unrelated to Search API:

  public function __construct(
    array $configuration, string $pluginId, array $pluginDefinition,
    ModuleHandlerInterface $moduleHandler,
    LanguageManagerInterface $languageManager,
    RendererInterface $renderer,
    SerializationInterface $yamlSerialization,
    TranslationInterface $stringTranslation,
    CacheBackendInterface $htmlCacheService,
    MimeTypeGuesserInterface $mimeGuesser
  ) {
    parent::__construct(
      $configuration, $pluginId, $pluginDefinition,
      $moduleHandler,
      $languageManager,
      $renderer,
      $yamlSerialization,
      $stringTranslation,
      $htmlCacheService
    );

    // Save dependencies.
    $this->mimeGuesser = $mimeGuesser;
  }

...which feels a bit comical just to inject one dependency. In this case, setter-injection seems like it would be better as it doesn't require the sub-class to be updated if the ancestor changes its __construct() parameters. What are your thoughts on this?