0

In Drupal 8, I needed to define services via the mymodule.services.yml file only, if I wanted to use them via the service container. I could also autowire them and the classes were automatically resolved.

services:
  my_module.twitter_feed:
    class: Drupal\my_module\TwitterFeed
    autowire: true

  domcrawler.crawler:
    class: Symfony\Component\DomCrawler\Crawler
    public: false

In Drupal 9, it looks like I must define all my custom classes inside the mymodule.services.yml file. Drupal 9 seems not to be able to identify my classes without an entry within the mymodule.services.yml file. There seems to be no autoresolving anymore. Symfony 4 and 5 are able to do that and Symfony 3.x (Drupal 8) was definitely able to autowire services and find classes by its own. Drupal seems to have its own service container, which suppresses that functionality.

Defining every single PHP class within a *.services.yml file is a huge amount of work for upgrading and maintaining my project. I cannot believe that Drupal 9 requires this work, because the Symfony framework already does that automatically.

Do you know any way to re-enable PHP class autowiring and autoresolving in an upgrade from Drupal 8 to 9?
I hope that anybody else was struggling with autowiring during an upgrade from Drupal 8 to 9 and knows any solution.

3
  • 2
    It's unclear what you mean by autoresolving. Autowiring is for injecting dependencies automatically, not to resolve classes.
    – 4uk4
    Dec 27, 2020 at 16:24
  • For injecting classes, the classes must be resolved in the first place.
    – jepster
    Dec 28, 2020 at 7:43
  • 1
    Maybe have a look at github.com/symfony/symfony/blob/… - seems there is a change in symfony about handling autowire. Dec 28, 2020 at 7:43

1 Answer 1

-3

I found out, that in Drupal 9 some autowiring "magic" from Drupal 8 was removed. Drupal also overrides the Symfony dependency injection by its own logic. Therefor you must write your own service provider class in your module to autowire your service containers (basically all PHP classes, except e.g. Drupal Plugins or Drupal Entities, which Drupal 9 resolves by its own).

Like this (path: modules/custom/my_nice_module/MyNiceModuleServiceProvider.php):

<?php
declare(strict_types=1);

namespace Drupal\my_nice_module;

use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\Finder\Finder;


class MyNiceModuleServiceProvider extends ServiceProviderBase {

  public function register(ContainerBuilder $container) {
    $containerModules = $container->getParameter('container.modules');
    $finder = new Finder();

    $foldersWithServiceContainers = [];

    $foldersWithServiceContainers['Drupal\my_nice_module\\'] = $finder->in(dirname($containerModules['my_nice_module']['pathname']) . '/src/')->files()->name('*.php');

    $foldersWithServiceContainers['Drupal\my_nice_module\Transformer\\'] = $finder->in(dirname($containerModules['my_nice_module']['pathname']) . '/src/Transformer/')->files()->name('*.php');
    $foldersWithServiceContainers['Drupal\my_nice_module\Transformer\RegionalCouncilMeeting\\'] = $finder->in(dirname($containerModules['my_nice_module']['pathname']) . '/src/Transformer/RegionalCouncilMeeting/')->files()->name('*.php');


    $foldersWithServiceContainers['Drupal\my_nice_module\DomCrawler\\'] = $finder->in(dirname($containerModules['my_nice_module']['pathname']) . '/src/DomCrawler/')->files()->name('*.php');
    $foldersWithServiceContainers['Drupal\my_nice_module\MongoDBFetcher\\'] = $finder->in(dirname($containerModules['my_nice_module']['pathname']) . '/src/MongoDBFetcher/')->files()->name('*.php');

    $foldersWithServiceContainers['Drupal\my_nice_module\Importer\\'] = $finder->in(dirname($containerModules['my_nice_module']['pathname']) . '/src/Importer/')->files()->name('*.php');
    $foldersWithServiceContainers['Drupal\my_nice_module\Importer\MongoDB\\'] = $finder->in(dirname($containerModules['my_nice_module']['pathname']) . '/src/Importer/MongoDB/')->files()->name('*.php');
    $foldersWithServiceContainers['Drupal\my_nice_module\Importer\RegionalCouncilMeetingsArchive\\'] = $finder->in(dirname($containerModules['my_nice_module']['pathname']) . '/src/Importer/RegionalCouncilMeetingsArchive/')->files()->name('*.php');

    $foldersWithServiceContainers['Drupal\my_nice_module\Entity\\'] = $finder->in(dirname($containerModules['my_nice_module']['pathname']) . '/src/Entity/')->files()->name('*.php');
    $foldersWithServiceContainers['Drupal\my_nice_module\Extractor\\'] = $finder->in(dirname($containerModules['my_nice_module']['pathname']) . '/src/Extractor/')->files()->name('*.php');
    $foldersWithServiceContainers['Drupal\my_nice_module\Service\\'] = $finder->in(dirname($containerModules['my_nice_module']['pathname']) . '/src/Service/')->files()->name('*.php');
    $foldersWithServiceContainers['Drupal\my_nice_module\DrushCommandFacade\\'] = $finder->in(dirname($containerModules['my_nice_module']['pathname']) . '/src/DrushCommandFacade/')->files()->name('*.php');

    foreach ($foldersWithServiceContainers as $namespace => $files) {
      foreach ($finder as $fileInfo) {
        $class = $namespace
          . substr($fileInfo->getFilename(), 0, -4);
        // don't override any existing service
        if ($container->hasDefinition($class)) {
          continue;
        }
        $definition = new Definition($class);
        $definition->setAutowired(TRUE);
        $container->setDefinition($class, $definition);
      }
    }
  }

}

Here is a good presentation about this: https://events.drupal.org/sites/default/files/slides/drupalcon.2019.autowiring.pdf

The Autoservices Drupal module can also be a good example for your own solution: https://gitlab.com/upstreamable/drupal-autoservices

4
  • 2
    Not sure if this is really a recommened approach. Registering any PHP Files as services? even Drupal entities? As far as I understand your services.yml file will be more or less empty then? Imho services.yml also does provide some kind of documentation about what services are available (at least I am used to look in there). But for parameters and aliases you still need to edit services.yml? Isn't that prone for errors if you just throw in a new file (with parameters) and think you can rely on that automagic. Dec 28, 2020 at 11:46
  • The entities here are custom ones, which are not tied to Drupal Entities. My use case was to re-enable autowiring after updating from Drupal 8 to 9. Therefor this approach solves my issue. If your module provides Drupal entities, then there is no need to autowire them via a service provider. There definitely are cases, where your service provider should not autowire classes, but as a general approach, this approach is the most efficient one. Otherwise you are using way too much time by editing a services.yml file.
    – jepster
    Dec 28, 2020 at 12:45
  • 5
    I agree with @StefanKorn. Normally Drupal modules don't use so many services. Importers, fetchers, transformers ... are usually plugins and it might be a better approach to define a plugin type (by creating a plugin manager) for each of them.
    – 4uk4
    Dec 28, 2020 at 13:51
  • It seems that my Drupal projects might be just larger than yours. I am working on a large and complex migration from a legacy CMS to Drupal 9. Which benefit would you get, if you write a plugin manager, instead of setting up services? I do not see a benefit here. You have a service which is computing something for you. I do not have blocks or anything else, which relies on a high level on abstraction and functionality from plugins. It could be helpful if you link any tutorial or docs here, which are describing a plugin manager benefit for the mentioned use case.
    – jepster
    Dec 29, 2020 at 7:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.