Symfony's autowiring
Tagged with: [ symfony ] [ autowire ] [ magic ]
When asking people if they use Symfony’s autowiring functionality, an often heard excuse why people don’t use it is because of all the magic that is happening during autowiring. But just like most impressive magic tricks, once explained, it all boils down to a few simple principles and Symfony’s autowiring is nothing different in that perspective. In this blogpost I will explain the new autowiring and autoconfiguration features, and why you should love them.
Defining services manually
You should already be familair with how we create services in Symfony: we must define the name of the service and the class to which the service point to. This is normally done in configuration files like services.yml
. These files can often be found within Symfony bundles, and are nothing more than a (long) list of services and their corresponding classes.
Once Symfony collected all these services, it will compile a service container with all services which then be used within your Symfony project.
When calling a service, Symfony tries to find the corresponding name in this service container, instantiate the corresponding class if not done so already, and returns it for you to use.
Let’s suppose I have a service that uses another service: a mailer service for instance may want to use the template render engine service in order to generate nice mail layouts. Symfony allows us to “connect” this template renderer to the mailer service by “injecting” it inside the mailer service.
In this example the render service is injected as a dependency into the mailer service. The mailer service itself isn’t aware of what kind of renderer this is: it might be a Twig template renderer, a smarty template engine, or even a plain PHP renderer. This allows for decoupling of services.
Defining these dependencies is done in the same services.yml
files, but we explicitly add dependencies to the services that needs them:
The arguments
option in the services.yml above defines which services (the renderer
service in this case) must be injected in the class. Symfony takes care of the correct order of things. When calling the mailer service, it will first instantiate the renderer class before it instantiate the mailer class.
Automatic generation of services
Now that we know how services are used within Symfony, let’s talk about autowiring. There are a few “tricks” happening during the autowiring process. The first thing is that Symfony automatically creates services from classes automatically without specifing this inside your services.yml
file.
Obviously, symfony cannot know what you like to call those services (like renderer
or mailer
), but instead it will register each class it finds as the fully qualified class name (FQCN). Our mailer service would automatically be registered as the service AppBundle\Service\MailerService
.
Symfony doesn’t do this automatically for all your classes: you must tell Symfony which directory or classes you want to autowire. This can be done withing your services.yml
:
With this in place, we tell Symfony that the all classes found inside the \AppBundle\Service
. For instance, Symfony would automatically create the \AppBundle\Service\MailerService
and the \AppBundle\Service\RenderService
service if these two services were inside the Service
directory.
If we need to inject arguments, we still must do this manually:
Here we have defined a mailer
service, which uses the RenderService
as a dependency.
Autowiring dependencies
Symfony’s second trick is what actually makes the autowiring. If we take a look back to our mailer class, we notice that the constructor uses a dependency:
Since we use a typehint to an object, PHP expects you to pass an \AppBundle\Service\RenderService
instance to this constructor. During autowiring, Symfony will look in the service container to see if there is such a service defined. If so, it will automatically inject that specific service into the constructor, even though you haven’t configured this service and added any arguments
to it.
This is what Symfony’s autowiring functionality is all about: Symfony automatically wires the dependencies based on their fully qualified class name.
In order for Symfony to autowire dependencies, you must explicitly tell this inside the service container:
In this example, all services found in the AppBundle\Service
directory will automatically be autowired.
When autowiring doesn’t work
It’s possible that autowiring doesn’t always work. This can happen when you need to inject a class and you do not have that class as a service defined in your service container. Or, it might also be possible that multiple services of the needed type exist. This happens when you typehint on for instance interfaces, or when you extend a class into multiple subclasses.
Suppose I have an interface called ImageLoaderInterface
, and three concrete classes: JpgLoader
, GifLoader
and PngLoader
. Now we have a generic ImageProcessor
class, that depends on one of the loaders:
If we used autowiring, Symfony cannot define which of the loaders needs to be autowired and will ask you to explicitly define this inside your service configuration. You could do this manually, but you could also create an alias to one of the loaders:
This way, whenever symfony finds the ImageLoaderInterface
typehint, it knows that it’s connected to the JpgLoader
.
When Symfony cannot autowire services, it will throw an exception and allows you to manually fix the issue inside your service definitions.
Autoconfiguration
Autoconfiguration is closely related to autowiring. Where autowiring will automatically connect any dependencies to services, autoconfiguration will actually configure those services automatically. This isn’t always needed, but you will see often that autoconfiguration is used for automatically adding tags to services.
Normally, when creating things like twig extensions, or custom form types, or even controllers as services, you’ll need to tag them with for instance twig.extension
, form.type
, kernel.subscriber
etc. This allows the container to find services tagged with specific tags during compilation of the container. The twig service for instance tries to find all services tagged with twig.extension
, and add those services as twig extensions so you can automatically use those extensions within Twig.
When using autowiring, many services are created automatically, so there is no way to add tags, unless you add all these extensions manually, which kind of ruins the whole point of autowiring.
With autoconfiguration, you can automatically, well, configure these kind of services. For each service that matches a certain class, parent class or interface (basically, if it matches the “instanceof” check), you can autoconfigure services.
For example, any twig extension you create must implement the Twig_ExtensionInterface
. Inside the main twig service, we configure that any service we discover and implements this interface, will be automatically tagged with twig.extension
. This is done in TwigExtension.php
:
This code autoconfigures services that are an instance of the Twig_Extension class and automatically tags them with twig.extension
.
A lot of these autoconfigurations can be found within the FrameworkBundle
:
This automatically tags commands, controllers, form types, guessers and a whole lot more.
Autoconfiguration your own instances
Besides using the registerForAutoConfiguration
method from the service container, it’s also possible to add your own autoconfiguration directly in your services.yml
. For instance, let’s suppose you want to autoconfigure any instance of ImageLoaderInterface
with the tag image.loader
:
Now, any service that is found and implements the ImageLoaderInterface
, will be automatically tagged with the image.loader
tag, AND will not be public services. There are more autoconfiguration options you can set, but often you will see this for tagging services mostly.
Note that you need to add these to the special _instanceof
section in your services.yml
.
Setting defaults
Sometimes you need to set certain defaults for each service you define. This can be done with the special _defaults
section in your services.yml
:
This will automatically autowire and autoconfigure all services found in the current services.yml file, and will set them to private services.
Notes
There are a few things you need to consider when using autowiring:
-
They do not work on scalar typehints, only objects. Meaning you cannot autowire strings, ints or booleans for example. Symfony will automatically throw an exception in case it runs into this.
-
Contrary to many people’s believes, autowiring does not impose a performance hit since all services are eventually compiled into a cacheable container file. However, when in
dev
mode, it might recompile more often when you modify classes but unless you have very large projects, that should not be a problem. -
There is absolutely no need to use autowiring. Everything with autowiring can be done with manually defining services too. Autowiring just removes the need to writing large service files with boilerplate. You are not missing anything out when you opt for not using autowiring.
-
Autowiring is not magic. It’s just Symfony trying to automatically inject services with other services based on their fully qualified class name.