I’ve been struggling around recently with running a Symfony 2.7 project having it’s configuration coming from environment variables.

I’d like to present my current solution in case of someone else might find it useful. Or someone else knows a better solution and would like to contact me so I can write a follow up.

Why Configuring an Application via Environment Variables

12factor.net sums that up nicely:

You don’t mix configuration and code. Chances are then significantly lower that you accidentally check credentials into the repository.

Letting the application run on a machine is as easy as setting the needed environment variables and starting it. And so you might be able to just throw it into a Paas and call it a day.

The Goals

In this case, the application has to run in three scenarios without hazzle.

First, local development. I want to clone the application and adjust maybe like one or two settings and let go. Those are development settings which are ok to have in the repository.

Second, running it on a server. This is where I want to have everything in environment variables without interfering with the development configuration. Currently, the application is running on a pretty standard Ubuntu 14.04 server with Apache. In the near future, we want to host some applications via Flynn which should work then out of the box. But more on that in a different posting.

And third, we use SensioLabs Insight for static code analysis. When they scan the code, they boot up the application and so every setting has to have some value, no matter what.

The Current Solution

Here are the three bits to cover the goals.

Local Development

The first goal is achieved by importing a parameters_dev.yml within the config_dev.yml like this:

imports:
    - { resource: config.yml }
    - { resource: parameters_dev.yml }

The parameters_dev.yml contains only local development settings like localhost for the database host.

Running it on a Server

Here we use environment variables. This is how the process is documented on the official site: How to Set external Parameters in the Service Container

This is our parameters.yml.dist which is used as a template for generating the parameters.yml on deployment:

parameters:
    database_driver:    pdo_mysql
    database_host:     "%database.host%"
    database_port:     "%database.port%"
    database_name:     "%database.name%"
    database_user:     "%database.user%"
    database_password: "%database.password%"
    # and so on

And the values of those environment variables are fed by the Apache configuration of the server:

SetEnv SYMFONY__DATABASE__HOST "abc"
SetEnv SYMFONY__DATABASE__PORT "3306"
SetEnv SYMFONY__DATABASE__NAME "theDB"
SetEnv SYMFONY__DATABASE__USER "anUser"
SetEnv SYMFONY__DATABASE__PASSWORD "andHisPassword"
# and so on

SensioLabs Insight

So far, this has been straight forward. But SensioLabs Insight breaks a few things here. It runs composer install and boots the application in the production environment. This fails as of course there are no environment variables set.

Lucky fact: It doesn’t matter what’s inside the variables as Insight doesn’t perform any HTTP requests or similar.

What we need here is some kind of fallback like “if we are in production and no environment variable is set, load some dummy values”. And this is how we do it:

First of all, lets modify the config.yml to load a PHP configuration file:

imports:
    - { resource: fallbackEnv.php }
    - { resource: parameters.yml }

And here is the content of the fallbackEnv.php performing the check for the environment variables:

<?php
if (!getenv('SYMFONY__DATABASE__HOST')) {
    $this->import('noEnvFallback.yml');
}

We assume that if one variable is set, then all of them are available. We could load now directly the dummy values, but the yml notation is more convenient. So lets stick with that via importing the noEnvFallback.yml. This file contains the missing variables:

parameters:
    database.driver: pdo_mysql
    database.host: ''
    database.port: ''
    database.name: ''
    database.user: ''
    database.password: ''
    # and so on

Note that it doesn’t matter for the development environment that it loads the dummy values as directly afterwards, the parameters_dev.yml is imported.

Tada, Insight doesn’t complain anymore and we covered all of our goals.