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;
}
// ...
}
?>
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()
orcreateInstance()
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 thecreate()
orcreateInstance()
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.
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.
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.
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 usesInlineFormErrorsServiceProvider
. - 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')]);
}
}
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: falseThe
decorates
option tells the container that theApp\DecoratingMailer
service replaces theApp\Mailer
service. The oldApp\Mailer
service is renamed toApp\DecoratingMailer.inner
so you can inject it into your new service.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']
- drupal.org - Services and dependency injection in Drupal 8
- drupal.org - class Drupal
- drupal.org - Service Tags
- drupal.org - Service and Dependency Injection Container
- symfony.com - How to Decorate Services