Symfony2: Avoiding coupling applications to the environment

A quick post looking avoiding tying a Symfony2 application to the environment and instead varying config per environment.

It’s tempting to access the env parameter from within an application in order to make decisions. A very crude example designed to make what is happening clear, is getting the environment within from the container in a controller and using that to decide whether to actually send an SMS alert:

We could clean this up by moving the sending of the SMS into a service and injecting the value of the parameter into it:

with this service definition:

However whilst moving the sending of the SMS out of the controller is a good idea we are still coupling the service to the environment.

Why this is bad in theory

The application should not be aware of what environment it is running under, it should just be configured a particular way based on its configuration files. Since we want to vary configuration between different environments there are typically different configuration for each of these environments. We choose which of these sets of configuration to load rather than telling the application which environment it is in.

Why this is bad in practice

This is all very well in theory but what problem’s can it cause us to make the application aware of what environment it is in in practice? The first is that we can only change the configuration now in the code if we change our mind about the setting. The second is that we can no longer test the setting by changing config, we need to change the environment the application is running in altogether to see the effect, if we used a configuration value instead we could just change that for the current environment.

If you later need to add further environments which need the same change then you will need to add to the if statements in the code to do this. With a configuration value you can just set it for that environment if it needs a value different to the default configuration.

With a good parameter name it is then clear as to its purpose as well, so reconfiguring the application is straightforward without having to dig into the application code to see what the parameter is used for.

with this service definition:

and in the config_dev.yml file:

Rather than simply setting this as a parameter you could expose it as bundle configuration as I looked at in my previous post.

Symfony2: Yet more on that Twig Extension

In my last post I looked at the end at creating a Twig extension that would return the template to extend in order to pull the logic for the decision out of the template. The template names were just stored in class constants to keep the example simple; I mentioned that these could be moved out into configuration. A few people asked for more details on how that would be done so here it is. This is not specific to Twig extension but it does provide us with an example to look at exposing bundle configuration.

So to start with the extension class looks like this:

with this service definition:

As we only have a couple of template names we could inject them individually to the constructor:

and set them as parameters in our services file:

It may be better though to just accept an associative array of template names so that we can increase the number later without increasing the number of constructor arguments:

So far this has just used setting parameters directly, however this does not give us much opportunity to validate the configuration. Currently the parameters are set in our bundle as well. If we decide that this bundle is worth sharing between applications then we would want to be able to set the parameters outside of the bundle in the app level config. We could just leave setting these parameters directly and move to doing that in the app parameters file:

This still does not give us much control over what values get set. If we move to making them part of the config that the bundle exposes to the app level config then we can validate the values. It also makes it easier for someone else or ourselves in the future to see how to configure the bundle by looking at the allowed config.

We can instead make setting the template part of the bundle configuration instead of setting the parameters directly. This is the way the bundles that are part of the framework handle their configuration. The first step is to tell the bundle to expect configuration relating to the templates in the Configuration class that is in the DependencyInjection directory. This is autogenerated if you create your bundles with the generate:bundle console command:

Here we are saying that we want an array of templates, at the moment all we have said is that it must exist and that it must have at least one element. We will now not be able to run the application as we get an exception due to not having met these requirements.

We can now add them to the config.yml file, instead of the parameters section we add them to a section with the top level key which matches out bundle’s name which was set as the root node name in the configuration class above. For the examples this is “acme”:

At this point we can remove the previous setting on these values in the parameters section of the config.

We should now be able to load the application without an exception about the config, however the template values are not yet being set as parameters. For that we also need to take the validated config and directly set the parameters. This is done in the Extension class in the DependencyInjection folder which is again generated automatically. In that we can see the configuration being processed in the first two lines of the load method. This is where the config files such as config.yml and config_dev.yml are merged and validated against the tree in the Configuration class.

We can use the processed configuration array to access the values and set them as parameters:

We are now back where we started and had to make a few changes to get there, so what have we actually gained? Not much so far and our Twig extension is still expecting particular keys to be set in that array, which is going to cause issues if they are missing. We can start to get the benefits of the changes by tightening up the validation settings in Configuration class.

We do not just want an array with some values in this case we want specific keys to be set. So to ensure that the “full” and “partial” keys have values we can change the configuration to:

If we usual use the same name for these templates then we could set default values and use convention to avoid the explicit configuration but still leave open the possibility of overriding these values if we need to:

There is a console command to dump the config, showing the available key and any default values. This is dumped as Yaml so you can copy it as a starting pint for configuring the bundle. You can run it for this demo bundle with app/console config:dump-reference acme.

We can add additional information to this from the configuration tree using the info() and example() methods. Info is printed above the key it is set for as a comment; examples are printed as an inline comment after the value. The example is less useful where we already have default values so let’s go back to version where we didn’t and enhance it with some additional information:

Running the console command now gives us:

So we have made our bundle more robust by validating its configuration and made more information about this configuration available to future uses of it. There are a lot more options available when validating configuration as there are a lot more possible requirements around what config is needed. There is more information on the possibilities when defining configuration in the official documentation.