Creating services with optional dependencies in Drupal 8

Submitted on Thu, 06/07/2018 - 11:08

Today I encountered a problem I did not think about earlier. After I pushed a fix to a project I am working on, the CI builds started showing errors. And the problem was coming from a message like this:

The service "mymodule.attachments_manager" has a dependency on a non-existent service "metatag.manager".

In many cases when you see that, it probably means your module was installed before a module it depends on. For example, in this case, it would seem that this module depends on metatag, and so declaring it as a dependency would fix the issue. And for sure, it would. But sometimes dependencies are not black and white.

This particular service does some handling of the attachments when used together with metatag. It does so, because it is a module we use across projects, and we can not be sure metatag is used in any given project. So it's only used in a way that is something like this:

Now, what this means, is that for the attachments manager to be useful, we need metatag. If we do not have metatag, we do not even need this service. So basically, the service depends on metatag (as it uses the service metatag.manager), but the module does not (as it does not even need its own service if metatag is not installed).

Now, there are several ways you could go about fixing this for a given project. Creating a new module that depends on metatag could be one way. But today, let's look at how we can make this service have an optional dependency on another service.

Now, this could never work if this module was installed before metatag (which it very well could, since it does not depend on it). A solution then would be to make the metatag.manager service optional. Which is something we can do by removing it from the constructor and create a setter for it.

So there we have it! The service can be instantiated without relying on the metatag.manager service. And if it is available in the service container, the method setMetatagManager will be called with the service, and our service will have it available in the cases where we need it.

Now let's finish off with an animated gif related to "service container".