Michael Dyrynda
Home Blog Podcasts
 
Simple environment switching in Laravel 5 February 26th, 2015

In Laravel 4, we had configuration along the following lines:

1app/config/
2 app.php
3 auth.php
4 cache.php
5 compile.php
6 database.php
7 local/
8 app.php
9 auth.php
10 cache.php
11 database.php
12 mail.php
13 production/
14 app.php
15 auth.php
16 cache.php
17 database.php
18 queue.php
19 remote.php
20 services.php
21 session.php
22 testing/
23 cache.php
24 session.php
25 view.php
26 workbench.php

In addition to this directory structure, we then had environment configuration files for each environment - .env.php, .env.local.php, .env.testing.php.

With the old structure, it was necessary to create the directory structure for anything we wanted to change per-environment, in addition to a .env.*.php file for any sensitive configuration we wanted to keep out of version control. This approach is quite messy.

With the introduction of Laravel 5, this configuration structure was flattened:

1config/
2 app.php
3 auth.php
4 cache.php
5 compile.php
6 database.php
7 filesystems.php
8 mail.php
9 queue.php
10 services.php
11 session.php
12 view.php

We now also have a single .env file.

But how do we configure things for different environments now? This is madness Taylor!

In flattening the structure, what we have now is a much simpler way of configuring the application and moving it between environments (homestead, Digital Ocean, S3). That's the important thing to note here; that environments are generally completely independent of each other.

So now, you have different configuration parameters for your database. On your local environment, you might be using SQLite. On your production environment, you're probably using MySQL or PostgreSQL. How is that configured?

1<?php
2 
3return [
4 'fetch' => PDO::FETCH_CLASS,
5 'default' => env('DB_DRIVER', 'mysql'),
6 'connections' => [
7 'sqlite' => [
8 'driver' => 'sqlite',
9 'database' => storage_path() . '/database.sqlite',
10 'prefix' => '',
11 ],
12 'mysql' => [
13 'driver' => 'mysql',
14 'host' => env('DB_HOST', 'localhost'),
15 'database' => env('DB_DATABASE', 'forge'),
16 'username' => env('DB_USERNAME', 'forge'),
17 'password' => env('DB_PASSWORD', ''),
18 'charset' => 'utf8',
19 'collation' => 'utf8_unicode_ci',
20 'prefix' => '',
21 'strict' => false,
22 ],
23 ],
24];

Your .env file will simply contain the parameters that change from deployment to deployment.

1DB_HOST=localhost
2DB_DATABASE=mydatabase
3DB_USERNAME=myusername
4DB_PASSWORD=5up3r53cr37

The env() helper takes two parameters - the key to retrieve from the .env file and a default to be used if it hasn't been set. Based on my database.php and .env files above, we can see that for this environment we'll be using the mysql driver and our credentials will be set based on values in the .env file.

Laravel 5 shifted to using PHP dotenv, which enables us to use the .env file, in lieu of proper environment variables.

You should never store sensitive credentials in your code. Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environments – such as database credentials or credentials for 3rd party services – should be extracted from the code into environment variables.

What does that mean? When you deploy to your production environment using, for example, Laravel Forge you would actually set these configuration parameters via the web interface. The .env file doesn't even exist!

PHP dotenv allows us to be agile in adding, changing, and removing environment variables without having to mess around with virtual hosts in Apache or nginx, adding php_value keys to .htaccess files and so on. When we're developing, these things can change quite often. Once we're in production, once they're set, they're set (unless you're adding new ones, or updating credentials).

For testing environment, these settings can be changed in your phpunit.xml file. Remember - you don't need to have the messy structure of Laravel 4.

1<php>
2 <env name="APP_ENV" value="testing"/>
3 <env name="CACHE_DRIVER" value="array"/>
4 <env name="SESSION_DRIVER" value="array"/>
5</php>

So what if you still really want to switch between environments? In development, it's actually quite simple. You can create multiple environment files - .env.dev, .env.local, .env.testing - then symlink between them creating a reference to whichever you want in your .env file.

Now, in doing this, you would need to make sure that you update .gitignore to ignore .env and .env.* to make sure you don't accidentally commit any sensitive information to version control. From there, it's a matter of flicking the switch.

Here's a simple bash function you can use - just drop it in your .zshrc or .bash_aliases file, whichever is appropriate for the shell you're using.

1function envswitch() {
2 if [ ! "$1" ]
3 then
4 echo "Missing required parameter envname"
5 echo "File should exist in current directory as .env.envname"
6 echo "Usage:"
7 echo " envswitch envname"
8 return
9 fi
10 
11 ENV_PATH="./.env"
12 ENV_LINK=".env.$1"
13 
14 # Ensure the file being sylinked exists
15 if [ ! -e "$ENV_LINK" ]
16 then
17 echo "Error: "$ENV_LINK" does not exist in current directory"
18 return
19 fi
20 
21 # If the ENV_PATH already exists, remove it
22 if [ -e "$ENV_PATH" ]
23 then
24 rm -f "$ENV_PATH"
25 fi
26 
27 # Symlink the file to $ENV_LINK
28 ln -s "$ENV_LINK" "$ENV_PATH"
29}

Source the file (. ~/.zshrc), then you can run envswitch dev, which will symlink your .env.dev file to .env and you will have all your dev environment variables.

I'm a real developer ™
Michael Dyrynda

@michaeldyrynda

I am a software developer specialising in PHP and the Laravel Framework, and a freelancer, blogger, and podcaster by night.

Proudly hosted with Vultr

Syntax highlighting by Torchlight