To understand service decorators, you’ll need to have working knowledge of dependency injection and how it is used in Symfony or Drupal 8. If you’re new to the topic, it would be worthwhile to read some more about it before going on.
What are service decorators?
While working with services in Drupal 8 or in Symfony, you might have come across a situation where you wanted to alter an existing method OR add a new method to an existing service.
You might have thought of creating a new service by extending the existing one (this is called the chain-of-responsibility pattern) by extending ServiceProviderBase. Here is an example from the token module, which replaces Drupal Core’s token service class.
Here, the setClass method defines the new class, which extends Drupal Core’s token class. But this will replace the original service class—which is helpful if you want to alter it once and for all. What if you need to extend the service in different custom modules? In other words: you want some functionality of the service to be available only if the module is enabled (like in a multisite, where different modules are enabled on different sites). Replacing a service class doesn’t really give this flexibility. If multiple modules alter the same service definition, only the last one will take effect, which is not helpful when you want to extend functionality with each module enabled.
The concept that allows us this kind of functionality is Service Decorators, a feature provided by Symfony. It is based on the decorator pattern of Gang of Four (GoF) design patterns. With this, you can use the same service class, and also add as many methods as required or modify existing methods of the service.
How to use them
By now, you know what kind of issues service decorators can solve. Let’s deep dive into how we can implement service decorators. (The first, most important thing for this is to have an interface class for your service.)
Suppose you have a service:
And here’s the class OriginalService:
Normally, you can use this service as:
Which will give you the output: "current page uri: /node"
Suppose you want to change (decorate the existing service) the “helper()” method of this service. For that, you have to define a service in your custom module.
Let’s understand this definition:
decorates: The key contains the service that you want to override.
decoration_priority: Set the decoration priority, where higher priority runs first.
public: If you are not going to access this service directly, set it to false. By default, for our case we will set it to false. :)
arguments: Pass the old service as an argument like “NEW_SERVICE_NAME.inner”. It will be possible to use functions of the original service class, and you also have to pass all the arguments required by the original services with this new service definition.
So, the final OriginalServiceOverride will look something like this:
Now, without changing the service, you can get the output from the new service that you defined, and you can also use the new function added in this service.
And this will generate the following text as output: “Overridden helper”;
Here’s the complete sample code: https://github.com/miteshmap/service_decorator
Liked this post? Let us know what you think below.
Mitesh Patel, Technical Architect
Off work, Mitesh prefers to be next to his wife watching the latest sci-fi flick, or laughing at his own (incredibly dry) jokes.