The Symfony service container that Drupal 8 ships with allows us to define a large number of services (dependency objects) that we can inject in our controllers, forms, plugins, other services, etc. If you don't know about dependency injection yet, you can read more about it here. In this article we will look at how we can use our own factory class to instantiate a service via the Symfony - Drupal 8 service container.
The typical (barebones) service definition consists of a class name to be instantiated and an array of arguments to be passed to its constructor as it gets created (other service definitions or static parameters). For more information, check out the documentation on services.
In some cases, though, we would like our service to be built dynamically based on certain contextual conditions, such as the current user. The implication is also that we don’t rely on the service container for the actual object instantiation, but our own factory class. We do still want to benefit from most of what the container offers us, such as caching.
Let’s see a very simple example. Imagine a UserContextInterface
which can have multiple implementations. These implementations depend on some value on the current user account (such as role for instance). And we want to have a service we can inject into our other objects which implements this interface but which is also the representation of the current user. Meaning it is an implementation specific to it (not always the same class).
We can go about achieving this in two ways:
- We can have a Factory class we define as a simple service (with the current user as an argument), use this as our dependency and then always ask it to give us the correct
UserContextInterface
. - We can have a Factory class we define as a service (with the current user as an argument) but use it in the definition of another service as a factory and rely on the container for asking it for the
UserContextInterface
.
The first option is pretty self-explanatory and not necessary in our case. Why should we keep asking the user context at runtime (the process to determine the context can be quite complex) when we can have that cached for the duration of the request. So let’s instead see how the second option would work:
my_module.user_context_factory:
class: Drupal\my_module\UserContextFactory
arguments: ['@current_user']
my_module.user_context:
class: Drupal\my_module\UserContextFactory
factory: 'my_module.user_context_factory:getUserContext'
So these would be our service definitions. We have the factory which takes the current user as an argument, and the user context service which we will be injecting as our dependency wherever we need. The latter uses our factory’s getUserContext()
method to return the relevant UserContextInterface
implementation. It is not so important what class we set on this latter service because the resulting object will always be the result of the factory.
The rest is boilerplate and we won’t be going into it. However, what needs to happen next is create our UserContextFactory
class which takes in the AccountProxyInterface
representing the current user and which implements the getUserContext()
method tasked with building the UserContextInterface
implementation. The latter method is not bound to any return type by the service per se, however, we must ensure that we return a UserContextInterface
in every case to preserve the integrity of our application. One good practice to ensure this is creating a UserContextNone
implementation of UserContextInterface
which would be returned by the factory in those edge cases when the context cannot be determined or values are missing, etc.
So that is pretty much it on how and why you would or can use a factory instantiation of services from your container. There is nothing new here, in fact the Symfony documentation has an entry specifically about this. However, I believe it’s a neat little trick we should all be aware of.

Daniel Sipos
Danny founded WEBOMELETTE in 2012 as a passion project, mostly writing about Drupal problems he faced day to day, as well as about new technologies and things that he thought other developers would find useful. Now he now manages a team of developers and designers, delivering quality products that make businesses successful.
Comments
Please provide example "service"
Hi -- Great article. But what "example" service is this providing? I haven't done much D8 work but is this like making a set of routes available to a limited set of users by Role, or is the Service a particular implementation of (I dunno) a data repository that should be only be visible to certain roles ?
Can you give a slightly more concrete example of what the possible Service could be, either in your business paradigm or a layer of your application ...
In reply to Please provide example "service" by David G. (not verified)
Advanced topic
Hey,
Sorry but this is a bit of a more advanced topic on services in Drupal 8 (Symfony). So if you don't yet have experience with basic service definition and implementation, feel free to read some more about that.
As for an example of service here, the article describes it (if not actually shows you all the code). Based on the explanation it should be pretty straightforward to write the factory and user context implementations.
UserContextInterface?
Thanks for the article. I'm not sure, if I got your example right, because it seems to be inconsistent with example given in https://api.drupal.org/api/drupal/core%21core.api.php/group/container/8.2.x. Shouldn't your example be:
In my understanding, this should create an object, which is an instance of
UserContextInterface
.In reply to UserContextInterface? by Miloš Kroulík (not verified)
The only difference I see is
The only difference I see is that for the
my_module.user_context
service you usedUserContextInterface
as the class. Which is fine. However, it is not mandatory. I used the factory class again, but it doesn't change anything. The service will return different things, can even return a string (if we let it) as the container cannot enforce anything. If it works the way you suggest, all the better.'The rest is boilerplate and
'The rest is boilerplate and we won't go into it' lol.
This reminds of the how to draw an owl instructable.
Add new comment