[plan] Service traits should require IoC injection

Created on 25 May 2016, over 8 years ago
Updated 21 February 2024, 10 months ago

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

🌱 Plan
Status

Active

Version

11.0 πŸ”₯

Component
OtherΒ  β†’

Last updated about 5 hours ago

Created by

πŸ‡ΊπŸ‡ΈUnited States mile23 Seattle, WA

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

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

  • πŸ‡ΊπŸ‡ΈUnited States mile23 Seattle, WA

    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.

  • Yes, there are plenty of uses in contrib.

    Deprecating it would probably help encourage contrib to use injection.

  • πŸ‡©πŸ‡ͺGermany donquixote

    @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.

Production build 0.71.5 2024