Skip to content

Latest commit

 

History

History
191 lines (155 loc) · 10.1 KB

4.4-essential-apis-services.md

File metadata and controls

191 lines (155 loc) · 10.1 KB

Services and Dependency Injection

From Services and dependency injection in Drupal 8

In Drupal 8 speak, a service is any object managed by the services container.

Drupal 8 introduces the concept of services to decouple reusable functionality and makes these services pluggable and replaceable by registering them with a service container. As a developer, it is best practice to access any of the services provided by Drupal via the service container to ensure the decoupled nature of these systems is respected. The Symfony 2 documentation has a great introduction to services.

As a developer, services are used to perform operations like accessing the database or sending an e-mail. Rather than use PHP's native MySQL functions, we use the core-provided service via the service container to perform this operation so that our code can simply access the database without having to worry about whether the database is MySQL or SQLlite, or if the mechanism for sending e-mail is SMTP or something else.

Services can depend on other services. For example, path.alias_manager depends on path.alias_storage, path.alias_whitelist, path.language_manager and cache.manager:

path.alias_manager:
   class: Drupal\Core\Path\AliasManager
   arguments: ['@path.alias_storage', '@path.alias_whitelist', '@language_manager', '@cache.manager']

This ensures these services are passed into the path.alias_manager constructor:

<?php
class AliasManager implements AliasManagerInterface, CacheDecoratorInterface {
  // ...

  /**
   * Constructs an AliasManager.
   *
   * @param \Drupal\Core\Path\AliasStorageInterface $storage
   *   The alias storage service.
   * @param \Drupal\Core\Path\AliasWhitelistInterface $whitelist
   *   The whitelist implementation to use.
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   *   The language manager.
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
   *   Cache backend.
   */
  public function __construct(AliasStorageInterface $storage, AliasWhitelistInterface $whitelist, LanguageManagerInterface $language_manager, CacheBackendInterface $cache) {
    $this->storage = $storage;
    $this->languageManager = $language_manager;
    $this->whitelist = $whitelist;
    $this->cache = $cache;
  }
  // ...
}
?>

Accessing Service Through the Container

When dependency injection is not applicable, you can use a service container to implement a service.

From Services and Dependency Injection Container:

Plugin classes, controllers, and similar classes have create() or createInstance() methods that are used to create an instance of the class. These methods come from different interfaces, and have different arguments, but they all include an argument $container of type \Symfony\Component\DependencyInjection\ContainerInterface. If you are defining one of these classes, in the create() or createInstance() method, call $container->get('myservice.name') to instantiate a service. The results of these calls are generally passed to the class constructor and saved as member variables in the class.

Access Service Through Service Location

If neither of the previous methods apply, Drupal can lookup the service for you by using \Drupal::service('service.name').

Drupal also provides many special methods for accessing common services. For example:

  • \Drupal::cache($bin) - Retrieves a particular cache bin based on specified parameter.
  • \Drupal::config($name) - Retrieves a configuration object based on specified parameter.
  • \Drupal::currentUser() - Retrieves current user service.
  • \Drupal::database() - Retrieves database service.
  • \Drupal::entityTypeManager() - Retrieves the entity type manager service which is helpful for interacting with entities.

See class Drupal for more helper methods.

Service Tags

From Service Tags:

Some services have tags, which are defined in the service definition. Tags are used to define a group of related services, or to specify some aspect of how the service behaves. Typically, if you tag a service, your service class must also implement a corresponding interface. Some common examples:

  • access_check: Indicates a route access checking service; see the Menu and routing system topic for more information.
  • cache.bin: Indicates a cache bin service; see the Cache topic for more information.
  • event_subscriber: Indicates an event subscriber service. Event subscribers can be used for dynamic routing and route altering; see the Menu and routing system topic for more information. They can also be used for other purposes; see http://symfony.com/doc/current/cookbook/doctrine/event_listeners_subscribers.html for more information.
  • needs_destruction: Indicates that a destruct() method needs to be called at the end of a request to finalize operations, if this service was instantiated. Services should implement \Drupal\Core\DestructableInterface in this case.
  • context_provider: Indicates a block context provider, used for example by block conditions. It has to implement \Drupal\Core\Plugin\Context\ContextProviderInterface.
  • http_client_middleware: Indicates that the service provides a guzzle middleware, see https://guzzle.readthedocs.org/en/latest/handlers-and-middleware.html for more information.

Creating a tag for a service does not do anything on its own, but tags can be discovered or queried in a compiler pass when the container is built, and a corresponding action can be taken. See \Drupal\Core\Render\MainContent\MainContentRenderersPass for an example of finding tagged services.

Overriding Services

To override the default classes used for existing services do the following:

  • Create a [Module]ServiceProvider class in the top-level of your module, but swap out [Module] for the camel-cased name of your module. For example, the inline_form_errors module uses InlineFormErrorsServiceProvider.
  • Service provider needs to implement \Drupal\Core\DependencyInjection\ServiceModifierInterface (which is commonly done by extending \Drupal\Core\DependencyInjection\ServiceProviderBase).
  • Add an alter() method to require Drupal use your class instead.

Example from inline_form_errors:

<?php

namespace Drupal\inline_form_errors;

use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Symfony\Component\DependencyInjection\Reference;

/**
 * Overrides the form_error_handler service to enable inline form errors.
 */
class InlineFormErrorsServiceProvider extends ServiceProviderBase {

  /**
   * {@inheritdoc}
   */
  public function alter(ContainerBuilder $container) {
    $container->getDefinition('form_error_handler')
      ->setClass(FormErrorHandler::class)
      ->setArguments([new Reference('string_translation'), new Reference('link_generator'), new Reference('renderer')]);
  }

}

Decorating Services

From symfony.com - How to Decorate Services:

When overriding an existing definition (e.g. when applying the Decorator pattern), the original service is lost:

# config/services.yaml
services:
    App\Mailer: ~

    # this replaces the old App\Mailer definition with the new one, the
    # old definition is lost
    App\Mailer:
        class: App\DecoratingMailer

Most of the time, that's exactly what you want to do. But sometimes, you might want to decorate the old service instead and keep the old service so that you can reference it:

# config/services.yaml
services:
    App\Mailer: ~

    App\DecoratingMailer:
        # overrides the App\Mailer service
        # but that service is still available as App\DecoratingMailer.inner
        decorates: App\Mailer

        # pass the old service as an argument
        arguments: ['@App\DecoratingMailer.inner']

        # private, because usually you do not need to fetch App\DecoratingMailer directly
        public:    false

The decorates option tells the container that the App\DecoratingMailer service replaces the App\Mailer service. The old App\Mailer service is renamed to App\DecoratingMailer.inner so you can inject it into your new service.

Decoration Priority

When applying multiple decorators to a service, you can control their order with the decoration_priority option. Its value is an integer that defaults to 0 and higher priorities mean that decorators will be applied earlier.

# config/services.yaml
Foo: ~

Bar:
    public: false
    decorates: Foo
    decoration_priority: 5
    arguments: ['@Bar.inner']

Baz:
    public: false
    decorates: Foo
    decoration_priority: 1
    arguments: ['@Baz.inner']

Additional Resources