Allow Plugins to specify Services via Annotation

Created on 6 October 2017, about 7 years ago
Updated 28 April 2023, over 1 year ago

Problem/Motivation

To call a service from a Plugin, it is recommended to use dependency injection rather than referencing \Drupal::service() directly. As more helper functions, such as format_date() are being deprecated in favor of direct service calls, more and more plugins are needing to use services.

Adding dependency injection to a plugin currently requires a fair amount of detailed "boiler-plate" code to implement the ContainerFactoryPluginInterface: adding a constructor, adding a create() static method, getting services from the container, passing them to the constructor, creating class properties to hold the services, and a lot of comments needed to pass codesniffer.

This is a large burden of effort, especially for more junior devs who are just trying to create a simple custom block to wrap a front-end twig template and need to call something like the date.formatter service. Suddenly, a previously simple call to format_date() becomes a large effort (or more commonly, they just ignore it and don't implement dependency injection).

Proposed resolution

To improve this situation, I propose adding the list of desired services to the plugin Annotation, then simply adding a Trait to the plugin that handles all of the dependency injection.

The included patch does this. First, it adds a new "ServiceFactory" to the Plugin system that extends the existing "ContainerFactory". After performing the normal parent functions, it loops through the "services" array in the plugin definition (annotation) and fetches the desired services from the current container.

The new PluginServiceInterface defines the getter and setter for the services that were fetched from the container.

The new PluginServiceTrait implements the getter and setter, currently by keeping the array of services. In the future it could potentially set properties of the plugin directly, but the service ids would need to be converted to valid property names (left as a todo)

Thus, in practice, to convert a plugin (such as a custom block) from using \Drupal::service('service_id') into properly using dependency injection, you add the 'service_id' to the 'services' array in the annotation, add the PluginServiceInterface to the implements section, and add the "use PluginServiceTrait" (along with adding the needed "use" statements at the top). Instead of calling \Drupal::service('service_id') the plugin calls $this->getService('service_id'). About as simple as we can make it.

Example code:

namespace Drupal\my_date_block\Plugin\Block;

use Drupal\Core\Plugin\PluginServiceInterface;
use Drupal\Core\Plugin\PluginServiceTrait;
use Drupal\Core\Block\BlockBase;

/**
 * Provides my custom block.
 *
 * @Block(
 *   id = "my_date_block",
 *   admin_label = @Translation("My Date Block"),
 *   services = { "date.formatter" }
 * )
 */
class MyDateBlock extends BlockBase implements PluginServiceInterface {

  use PluginServiceTrait;

  /**
   * {@inheritdoc}
   */
  public function build() {
    return $this->getService('date.formatter')->format(time(), 'medium');
  }

}

Thanks to @eclipsegc for his help with this patch!

Remaining tasks

Test needed.

User interface changes

None.

API changes

None. Only plugins that implement the new PluginServiceInterface are affected.

Data model changes

None.

Feature request
Status

Closed: duplicate

Version

10.1

Component
Plugin 

Last updated about 16 hours ago

Created by

🇺🇸United States mpotter

Live updates comments and jobs are added and updated live.
  • Needs issue summary update

    Issue summaries save everyone time if they are kept up-to-date. See Update issue summary task instructions.

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.

Production build 0.71.5 2024