Symfony2: Configuring different services for different environments

In my previous post I talked about avoiding optional dependencies. The example I used was of changing an optional dependency on a logger into a mandatory one. We injected a null logger implementation when we did not need logging. In this post I am going to look at this from the configuration point of view. In particular, how we could switch between which implementation gets injected for different environments.

We would not want to turn logging off for production but we may want to for an environment used for automated tests.

So we have the following services file:

For our test environment we want to use the null logger implementation from PSR instead. So we need to inject a different logger service into all the services that use it. So what can we do? We could load a different xml file for the test environment by changing the extension class.

If we load a different one instead then we will have a lot of repetition. We would be better off loading an extra one and overriding services as necessary. We could define a new logger service and redefine all the services that use it:

This is still a lot of repeated configuration. We could redefine the logger service instead so that this is then used throughout.

This is an improvement as we now just have one different service in our extra service file. It would be good to reduce the changes further still. We can do this using a service alias. Then we can just change what the alias looks like. We can create an alias that points at the normal logger service. Then we can use this in our other services:

We can now set the alias to point at the normal logger service in our main service file. We can rename the null logger service id. Now that it does not clash with the main logger service we can move it to the main services xml file. In the extra services file we now just need to point the alias at the null service.

This does not make much difference as the null logger has no arguments. We could get rid of it altogether though. We can make the choice of service to alias to a parameter:

It can now be set in the application config and the extra services file removed altogether!

and

As well as making the reconfiguration simpler we have decoupled the choice from the environment. I have previously written about this as a more general idea. We can now change the parameter on its own. This may not be that important for our logger but allows us to switch which services we use with ease.

Avoiding Optional Dependencies

In my previous post I argued against setter injection. Optional dependencies are one of the main objections raised in the comments and elsewhere. I did mention these with a suggestion of just making them optional constructor arguments. I also mentioned that refactoring to stop it being optional was a solution.

I think that this is worth exploring further. I think that the disadvantages of setter injection means it should be avoided. I do not think any advantages for dealing with optional dependencies outweigh the disadvantages. This is not an opinion shared by all though.

Looking at how I deal with optional dependencies made me realise that I do not just avoid setter injection. I also usually avoid having optional dependencies. Having thought this through more will now avoid using them at all.

So why are optional dependencies a problem? Often an optional dependency is a sign that the class has several behaviours. So it has several responsibilities and is not adhering to the Single Responsibility Principle. A class with an optional dependency does one thing always. It does another thing sometimes if the optional dependency is present.

We would be better extracting the optional behaviour into another class. This new class could then have a single responsibility for that behaviour. In the new class this would not be optional and we can now choose which version. We are now deciding if we want the optional behaviour only in the config not in the config and in the object.

Even if this is not possible then we would still do well to avoid the optional dependency. Consider logging as the optional behaviour, which was a common counterexample. So a class with optional logging may look like this:

There is another issue here which is that we have to wrap any use of the logger is a conditional. If we do not then we would get a fatal error when calling the non-existent logger’s method. This adds complexity to the class and raises the risk of bugs. It is easy to avoid this bug with a spec or unit test. We could avoid even having to go to that effort though. Our class would be simpler if it looked like this:

We can have this and still achieve the desired behaviour of optional logging by using a Null object. We can pass in a null logger that implements the logger interface but which does nothing when we do not want logging. Our class does not need to be aware that this is the case, it will use anything that has the interface. In fact if you are using a PSR3 logger then there is null logger implementation in the psr/log package.

Yes, we need an extra class but optional logging is something we will find in many classes. For the sake of a single extra class we can remove conditionals from a lot of places in our code. This reduces the number of potential bugs and makes the code more readable. Removing a lot of conditionals in exchange for creating a single class is a good refactoring of code.

It is no more complicated to turn logging on and off in configuration. When we do not need logging the logger service can just be the null logger. We will have turned off logging without making any other changes.

We now have simpler code. We can remove all the conditionals. We avoid setter injection. All without adding anything more complex to our code than a simple null implementation of the logger.

My thought is that optional dependencies are themselves a code smell. Setter injection is not a solution to this smell. Stopping the dependency being optional is more important than how we inject it.

Avoiding Setter Injection

A while ago I had a twitter discussion with Igor Wiedler and Matthias Noback about setter injected dependencies being mutable.

When using objects as services in an application then we do not want them to have state. This is because we can them call them many times without the outcome changing. For example, if we have a mailer service that sends emails for us we would expect it to behave the same way whenever we call it in an application. We would not want the service’s behaviour to be different because we had already called it somewhere else in the request. We can make sure that we write the internals of services to not change their state in this way. Yet when we use setter injection we take control over this from the service and leave it in the hands of the code consuming the service.

The two main ways of injecting dependencies are constructor and setter injection. A main advantage of constructor injection is that ensures that we inject the dependency. This means that there are no surprises later if it has not been. Another advantage is that the choice of dependency is immutable. Constructing the service fixes it to that dependency (assuming we provide no other means of changing it).

With setter injection, we can call the setter method many times. So the choice of dependency is not immutable with setter injection. We cannot use the service knowing we will get the same results with the same arguments. Other code using the service could have changed the dependency between calls. Anywhere that uses the service can now set a different dependency which will change its behaviour.

For example, let’s say we can set a filter on our mailer service. Since this is optional we choose to inject it with a setter method. If we do not want a filter then we do not call the method. We could not expect consistent results if other code as other could change the filter dependency.

We could change the setter to only allow the dependency to be set once. We will need to write extra code needed for this. The main issue it will fall down because the of the optional nature of dependencies provided by setter injection. The behaviour remaining the same throughout the service’s lifetime is still not guaranteed. We could use the service before the dependency has been set and again after it has been set with different results.

We can avoid this by injecting the dependency through the constructor and making it an optional argument. Now the dependency is still optional but we are fixing whether it is set at the time of construction.

As an aside, it would be better to refactor away from having optional dependencies. Instead we could move to different classes implementing the service’s interface. One without the dependency and one where it is mandatory.

So is there a case for using setter injection at all? One other use is as an “adder” method which adds dependencies to a collection. Here calling it again does not replace the dependency but adds it to the collection. We still have the potential for changing behaviour here by adding additionally dependencies later.

If we want to fix the collection of dependencies what can we do? Well we can fix them at construction time by passing the full collection in as a constructor argument. So, is this always possible? Symfony2 uses adder methods to add dependencies outside of the original service definition. These dependencies are usually added in a compiler pass. The compiler pass finds tagged services and adds them as dependencies to another service. It does this by adding a call to the service’s adder method for each dependency in the service’s definition. Here is an example from the Symfony documentation:

The problem here is that it is still possible for code using the service to add more dependencies later. So can we prevent this? In the twitter conversation We discussed several options. One was to have a way of flagging that we have finished adding dependencies. The method could then throw an exception if called after the flag is set. We could do this by adding a method that sets the flag, calling it last in the service definition. Unfortunately, this feels like quite an awkward solution and relies on making sure that you add the call to the locking method.

Another would be to have a builder object that collects the dependencies and then set the service up with them. This requires adding an extra layer of complexity, which is not appealing. In fact, we realised that the service container already is doing this job. Just changing the way the compiler pass works would be enough. The TransportChain class’s first constructor argument is now a collection of transports. The compiler pass could now be as follows:

Here we are getting the first argument in the service definition for the transport chain. In this case we are assuming that it is already defined as a collection. We then append the references to the tagged services to the collection keeping any existing services. The argument in the definition is then replaced with the populated collection. We can now remove the addTransport method from the class. This prevents code using the transport chain from making further changes.

So it looks as though we can avoid most instances of setter injection with little difficulty. We then give ourselves the greater safety of constructor injection.