Symfony 3.3 Dependency Injection: ‘autoconfigure’ & 3rd Party Bundles
Quite recently, the good people of the Symfony team released version 3.3 of their incredible framework. This new version shipped with many new features and improvements to all sorts of components, most specifically the dependency injection component.
This article is written under the assumption that you (the reader) already has some knowledge about how Symfony 3 applications are written, how service definition configuration, bundles and compiler passes work with the Symfony framework.
One of the new features in Symfony 3.3’s dependency injection component was the ability to automatically configure services. To explain in short what this does: it enables you to leave out tag definitions if your own bundle’s services implement a specific interface that is now associated with the tag you would previously add.
For example, in Symfony 2.2 you would do this:
You would add the tag security.voter to your service to indicate to the security framework that your PostVoter class is a security voter.
Since Symfony 3.3 you can express the same functionality as this:
The tag security.voter would automatically be appended if you indicate that you want to autoconfigure your services within this service definition file, provided that your PostVoter class implements the appropriate interface for doing so:
This is now the case for a whole scala of service tags and functionalities that the Symfony framework ships with.
3rd party Bundles & Autoconfiguration
It’s nice that Symfony provides autoconfiguration for a lot of the components it ships with, but let’s take a look at how we can apply this to bundles that we’ve created ourselves to make service definition more elegant.
Let’s create ourselves a simple bundle that will expose the following functionality to the developer that incorporates it into his Symfony project:
- Expose a TaskRegistryService that lets you retrieve the service definition names of all ‘task’-type services defined in the service container, regardless of which bundle they’re located in.
- Check whether a certain ‘task’-type service is present within this TaskRegisteryService.
To not go into too much detail as to how to create a bundle by itself, I’ll briefly show you the core components that powers our bundle.
There’s our bundle’s extension class, that will load our bundle’s service definition from a YAML file:
There’s our task registry service, responsible for exposing the core functionality of our bundle:
There’s the service definition configuration, that wires our services into the container:
And finally, there’s a compiler pass that collects all service definitions tagged with example.task and adds method calls to the definition of our TaskRegistryService:
Together, this now allows us to use this bundle to define some ‘task’-style services in our application’s bundle and use service tags to append them into our task registry.
Let’s create some ‘task’-style services first:
And define them as services in our application’s service definitions:
And finally, output in a web controller a comma-separated list of the names of our tasks:
We did it, if you load the result in your browser you see the names of the tasks:
…but we’re still not using Symfony 3.3’s autoconfiguration functionality. Let’s go back to our bundle and make some changes.
The first thing we want to do is define an interface that will decide which service definitions should get our example.task tag:
Next up, we need to make adjustments to our bundle’s extension class to make it actually add the tags automatically:
As you can see, we’ve added a call to $container’s registerForAutoconfiguration method, and defined which tag it will add when our newly created interface was implemented.
Now we have to go back to our task implementations, and make them implement the interfaces:
And in our application’s service definitions, we no longer need to add the tags manually (provided that your app’s service definition has autoconfiguration turned on in the _defaults section of services.yml):
The end-result is the same, if you open our controller in the browser you’ll see the exact same thing as before:
Our app’s service definition configuration file now looks a lot cleaner, and our bundle is now far more elegant to implement into your application as a result.
There’s many ways that this new Symfony 3.3 DI functionality can be applied, and I hope that I managed to show you how you can apply it in your own/3rd party bundles to make them more elegant in use.
I’ve created an example project with the code that I showed above, you can check it out on GitHub: