Problem/Motivation

Follow-up to #2729643-15: Create LoggerChannelTrait

Creating a new trait for logger service discovery has led us to a problem:

If we want to require that the service trait use an injected service object, we end up breaking various callers.

Existing code which relies on, for instance, ControllerBase::getLogger(), but which does not properly use the ContainerInjectionInterface pattern, might break when the logger service wasn't previously injected.

This leads to a chicken/egg scenario where we have to figure out what to break and what to fix first. Some classes that use a service location trait will use the service setter of the trait during the factory method or during construction in order to inject the service. Others won't, which necessitates using \Drupal within the trait in order to discover the service. If we enforce the injection of the service by not using \Drupal in the getter, we then allow for poor Inversion of Control (IoC) practices. If we enforce IoC practices, however, we break existing code.

Proposed resolution

Note that this is a proposed resolution. :-)

Ensure that all service discovery traits have a setter for the service and make it clear that it's the best practice to use the setter in the class' factory method to perform injection.

Continue to use the \Drupal service pattern in these traits until classes which use them have been converted to the 'proper' IoC injection pattern. This means that traits will continue to have service accessors, and those accessors will use \Drupal to discover the service if it hasn't happened already.

Once those classes have been converted, have service traits enforce the IoC pattern. This means altering the behavior of the service accessors so that they don't use \Drupal, and instead fail if the service hasn't already been injected.

Remaining tasks

User interface changes

API changes

Data model changes

Comments

Mile23 created an issue. See original summary.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

mile23’s picture

Issue summary: View changes

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

mparker17’s picture

Issue summary: View changes

Changed the issue summary to clarify that IoC stands for "Inversion of Control" in the issue summary (for more information on the concept, see Martin Fowler's "Inversion of Control Containers and the Dependency Injection pattern").

@Mile23, does the following code demonstrate the "proper" Inversion of Control Dependency Injection Pattern?

namespace Drupal\example;

use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Logger\LoggerChannelTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;

class MyClass implements ContainerInjectionInterface {
  use LoggerChannelTrait;

  public function __construct() {}

  public static function create(ContainerInterface $container) {
    $myForm = new static();
    $myForm->setLoggerFactory($container->get('logger.factory'));

    return $myForm;
  }

}

... or if MyClass was a service ...

services:
  example.my_class:
    class: 'Drupal\example\MyClass'
    arguments: []
    calls:
      - [setLoggerFactory: ['@logger.factory']]
mile23’s picture

Thanks for the update.

Yes, that's the pattern for how to inject all the things using service traits: Inject by calling the setter in the factory method.

If I was reviewing code that defined a service that was composed of service traits, I'd send it back and say it shouldn't use those traits without a good reason. But it's not the end of the world, and the service definition lets us get away with it.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

solideogloria’s picture

Should the LoggerChannelTrait be deprecated, then, to encourage service injection and discourage new uses of the trait?

mile23’s picture

Searching through Drupal 11, I see that: A) LoggerChannelTrait is unused within core, and B) therefore no services follow this pattern. That's a step forward, I'd say... I assume the trait is still in use in contrib, which is why it's not deprecated at this point though maybe it should be.

Also, core now defines logger channels as services, rather than the factory, so you see this pattern in core.services.yml:

  logger.channel_base:
    abstract: true
    class: Drupal\Core\Logger\LoggerChannel
    factory: ['@logger.factory', 'get']
  logger.channel.default:
    parent: logger.channel_base
    arguments: ['system']
  logger.channel.php:
    parent: logger.channel_base
    arguments: ['php']

...

  views.entity_schema_subscriber:
    class: Drupal\views\EventSubscriber\ViewsEntitySchemaSubscriber
    arguments: ['@entity_type.manager', '@logger.channel.default']

So yay, even better.

solideogloria’s picture

Yes, there are plenty of uses in contrib.

Deprecating it would probably help encourage contrib to use injection.

donquixote’s picture

@mile23

Searching through Drupal 11, I see that: A) LoggerChannelTrait is unused within core, and B) therefore no services follow this pattern.

I do find it used in the following places in core:

core/lib/Drupal/Core/Controller/ControllerBase.php
core/lib/Drupal/Core/Form/FormBase.php
core/modules/layout_builder/src/Plugin/Derivative/FieldBlockDeriver.php
core/modules/jsonapi/src/ResourceType/ResourceTypeRepository.php
core/modules/image/src/Plugin/Field/FieldType/ImageItem.php
core/modules/views/src/ViewExecutable.php
core/modules/language/src/LanguageNegotiator.php

At least LanguageNegotiator is a service.
But the bigger usage is through base classes ControllerBase and FormBase, which also have other Drupal::*() calls.

I wonder if we should stop using these base classes with their hidden magic.

EDIT: Actually, ControllerBase and FormBase already have comments saying exactly what needs to be said.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.