Use env’s configuration in your application Symfony 4.1

In my last article, we’ve seen how Symfony 4 is powerfull to initiliaze & configure your dependencies especially with Flex’s paradigm.

Symfony 4.1 has been released since last month and brings lots of awesome features like Messager component (*) (thx Samuel ROZE), faster php router (**)(thx Nicolas Grekas) and others (thx all contributors :))

Amoung those, I will focus on one of them that I searched recently but which is ultimately childish simplicity. This is plain and I’ve found this pattern really clean.

Imagine you’ve got one simple download resources class. Its responsibility is to return the full path of a resource. You need to pass a resource as an argument and it will return the resource’s full path.

For this you need to configure your file’s provider. You can add your provider (like a CDN or internal directory) in a class’ constant.

But with this, you can’t test it easily with unit test.

Anyway, this is not a good idea because your different environments (development, integration & production) will become dependent of the “same” provider.

In fact you need to be able to define your provider by environment (.env ? :)

For this, usually in SF 2 & SF 3 you will define it in your parameters yaml. This is a good idea, sure ! 
In SF 4, parameters yaml pattern has been removed to use dotenv configuration.(It’s also especially powerfull with modern infra like Heroku’s env)

How to define one parameter with SF 4 ? Then how to use it in your application ?

But before to show you how to define & use your custom parameter, I will add one test class to make sure I don’t break my initial feature. (In fact, in the real life, I’ve add this test before, dat sure ;)) In this case, I’ve replaced constant by one public attribute in DownloadHelper (I could have use one private attribute with getter/setter but this is not the target so i simplified).

DownloadHelper with public attribute

So I define my expected like this.

Output console with 1 test, 1 assertion

Ok, now how to improve this and define env.configuration ?

To define your path in your environment’s configuration, this is really easy. You can use the same pattern like your database’s configuration. Add it in your .env.dist (then .env in your own development’s env).

In my .env file usually I add my own tokens :

“###> app environment configurations ###”

in .env to be consistent with Flex paradigm. (As a reminder, this tokens allow to uninstall one package and its configurations by Flex).

I add one <env /> for UPLOAD_DIR

Ok that’s fine but now how to use it in my application ? In fact this is very easy.

Since Symfony’s 4.1 you can use a new good feature ; the ParameterBagInterface (***). Once again, thx Nicolas Grekas for this. Add this ParameterBagInterface in your construct to use dependency injection and you will be able to access all your parameters.

ParameterBagInterface service in dependency injection

Launch your application & try it.

GOD, this is not compliant. It might be obvious to you but to be honest in my first time I thought. Sad, i’m an eternal dreamer :)

In fact don’t worry, you just need to define your env value like a parameter in your services yaml. When you open your services.yaml you can notice the parameters’ node. If you define your new env value with the appropriate syntax %env(), you won !

New parameter in services.yaml : upload_dir with %env() pattern

It looks good, of course if we launch our unit test, it will FAIL. We’ve removed old pattern to use .env configuration. And if you used frequently phpunit with SF 4 (in fact since the dotenv configuration), you know you need to define your env parameters in your phpunit.xml.

new <env /> UPLOAD_DIR in phpunit.xml

So just define it in and enjoy the result. (In this pattern you need to define the same value for each test’s env. However you can mock ParameterBagInterface to keep more flexibility. This is very easy with dependency injection).

Mocking ParameterBagInterface
Result phpunit execution

Some asked me why I’ve two assertions now ? This is just the expects($this->once()) in my mock who valid “get’s” method is just call one time. You can read this here, Example 9.11 Testing that a method gets called once and with a specified argument.

(*) : Message component presentation :

(**) : Faster php router announce : 
Author’s presentation :

(***) :