Symfony2: Dependency Injection Types

EDIT: Changes have been made to Dependency Injection in Symfony2 since I wrote this post, please read my follow up post after this one.

Symfony2 makes good use of Dependency Injection throughout and makes the Dependency Injection container available for code built on top of the framework. In this post I will look at the various ways of injecting dependencies into an object and how this is done in Symfony2. For more information on Dependency Injection itself, there is the wikipedia page and Martin Fowler’s introduction along with Fabien Potencier’s series of articles.

Symfony2 supports using XML, YAML and PHP to configure Dependency Injection, the recommended method for configuring Dependency Injection is XML so that is what I will look at here. There are several ways of injecting dependencies into a class that are supported in the XML configuration.

I am reusing the controller from my previous post, (StaticController refers to its use for static page content and not Static methods).

Constructor Injection

The dependencies are to be passed into the constructor, our class will look like this:

The relevant section of the XML config file looks like this:

Some advantages to this are that the dependencies must be provided to the object on instantiation so they must have been provided if accessed later. If the dependencies are necessary then this is an ideal way of ensuring they are provided.

There is a disadvantage that any runtime parameters cannot be passed to the object though as the Dependency Injection container is taking care of construction and must be passed in another way. Also the dependencies cannot be changed in the lifetime of the object which may be desired, if they do need to be changed than a setter method for this would need to be provided as well.

Setter/Method Injection

This is injection by calling a method of our class:

The XML would now be like this:

It is more difficult though to ensure that required dependencies have been injected before any methods of the object are called. It is of course possible to check that the dependencies have been provided before using them but constructor injection would do this for us without additional code.

If there are quite a few dependencies then it avoids a long constructor parameter list at the expense of a a lot of methods. If the list of dependencies is that long that perhaps some refactoring is called for.

One case where this is useful and which constructor injection is not a possible solution is where the dependencies are held in a collection within the object and there is a variable number, for example if a set of filters is to be run against some output. In this case there maybe no filters at all or a variable number. The setter method can be configured to be called the appropriate number of times in the XML file, this would not be possible with constructor injection except by passing the collection itself as the dependency.

Interface Injection

EDIT: Interface Injection has been removed from Symfony2 since I wrote this post, please read my follow up post for more information.

Symfony2 also allows for interface injection, in this case our class does not look much different from the setter method class except that it implements an interface containing those methods.

The advantage to this though comes in the XML file, passing the dependency to the object can be done by specifying the interface. This way all classes implementing the interface will have the setter method called with the dependency, this allows changing the dependency to be done in one place.

This is of course not always useful as we may want control over which particular dependency is passed into various classes, if you find though that the same one is required by multiple classes this is one way of simplifying the maintenance of the XML file.

Container Injection

This is where the container itself is injected, any objects can then be requested directly from it. Our class now looks like this:

and the XML:

The actual injection is easier as only the container is injected. This does however mean that there is an additional dependency on the container when testing. We either need to use a container to provide mock objects or mock the container as well.

Whereas with all the above methods the mocks can be directly injected without need for the container to be used in the unit tests. It also makes it more difficult to quickly see what dependencies an object has since they are not being clearly injected and any number could be being requested from the container throughout the class. Whilst it makes matters simpler when there a lot of dependencies that probably means refactoring is needed. I guess there may be a case for doing this during development whilst the dependencies of an object is an unknown and then once it has settled down moving to one of the other methods, however I would personally avoid this method of injection.

Summary

Apart from container injection which I think should be avoided, the other methods of injection each have their places depending on the nature of the dependencies. They are all well supported by Symfony2’s Dependency Injection container allowing the most appropriate to be chosen rather than being tied to a particular style.

Symfony2: Controller as Service

Background

I am recreating Lime Thinking’s current website using Symfony2, much of the site is made up of what are effectively static pages. The main content of the page is stored as an XML file which then has the overall XSL template applied to it to add the common parts of the site.

For the Symfony2 version I am using Twig rather than XSL and XML as this the default template engine. So my first controller is probably an atypical first controller to use. I want it to covert the current XML file to a twig template which references the overall Twig template and then render this as the response. The details of this process are very specific to our particular process and I will not bother you with, however I did find some interesting details of using Symfony2 controllers in the process which will share here.

First Controller

I started by using a controller which extended the base controller class, I needed a response object as a Symfony2 controller must return one, it also needed a request object as I was getting details of which XML file to use from a GET parameter. According to the book by extending the base controller I should just be able to retrieve both of these from the Dependency Injection container, so that my controller could look something like this:

Unfortunately in the version I was using (PR7) neither of these objects was actually available as a service, I could just ignore Dependency Injection and get my controller working in the following way:

Not really in the spirit of using Dependency Injection though. I found that I could add these services to my config.yml so that they would be available, adding the following allowed the first version of the controller to work:

Controller as Service

Whilst this is an improvement, it is still effectively Dependency Injection by container injection which is not what I want as it has many disadvantages as a form of Dependency Injection (I intend a follow up article with more details on why so will not discuss here). I would much rather be using constructor injection to get these objects into my controller. Fortunately, this is possible if I move away from extending the base class and instead define my controller as a service, I can then define my services within a bundle and inject them into the controller how I see fit.

In order to do this there are several steps, the initial further complication may not make the end result seem initially worth it as it is a simple example, but the additional control you get is in the long run. The following steps need to be taken:

  1. Change the way the controller is called in the routing config.
  2. Add a class to tell the framework how to load your service configuration.
  3. Modify the controller.
  4. Write a services configuration file.

Calling the service

Services are called slightly differently in the routing.yml file so the following:

needed changing to this

Service Configuration Loading

Doing this means that instead of the controller just being created directly it is requested from the Dependency Injection container, in order for this to be possible it needs to have had a service configuration file registered with it. We can do this by adding the following class, this is a very simplified version which will not allow for overriding of its settings:

This is called automatically as long as it is in a folder called DependencyInjection in the root of the bundle and is named BundleNameExtension. More complicated loading strategies can be found by digging about in the equivalent file in some of the other included bundles. This is enough to get going for now though.

The Controller

If we now modify our controller to be like this:

Note that it no longer extends the base controller and that we are injecting our dependencies via the constructor

Service Configuration File

Now by adding the following servcies.xml configuration file we can define the service routed to and its dependencies.

In this you can see that we are prefix our service ids with the alias from the service loading class. The first two services defined are the response and request, these are just effectively mapping ids to class names. Those ids are then used in the service definition of the controller in the nested argument elements. These elements tell the container to pass the referenced service as constructor arguments when instantiating the controller.

Symfony2: Routing and mod_rewrite

Quick note on Symfony2 routing and mod_rewrite.

As I am recreating an existing project using Symfony2 rather than starting from scratch there are a few things I a doing which may not fit the usual way of working Symfony2. One issue I have run across, and which is not immediately apparent, is with using mod_rewrite to try and fit our current URL scheme for the site to one which works with Symfony2 routing.

I used modrewrite to rewrite some URLs behind the scenes but the changes were not being picked up in the routing. After a bit of digging through rewrite logs to check that the rewrites were taking place it became apparent that when applying routing Symfony2 uses the originally requested URL and ignores any changes made by modrewrite.

Symfony2 does make use of modrewrite to remove the app.php or appdev.php from the URL that is needed without it. It does so though by rewriting the URL to app.php or app_dev.php with the original URL being removed rather than appended e.g.

is rewritten to

not

or

This means that there is no choice but to route using the original request as PHP has no access to the intermediate rewritten URLs. This is not particularly a problem if you set out using the Symfony2 way of routing from the start but it did stop me from being able to use a bit of mod_rewrite trickery to use our current URL schema with Symfony2 routing.