In Laravel 4, we had configuration along the following lines:
app/config/
app.php
auth.php
cache.php
compile.php
database.php
local/
app.php
auth.php
cache.php
database.php
mail.php
production/
app.php
auth.php
cache.php
database.php
queue.php
remote.php
services.php
session.php
testing/
cache.php
session.php
view.php
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:
config/
app.php
auth.php
cache.php
compile.php
database.php
filesystems.php
mail.php
queue.php
services.php
session.php
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?
<?php
return [
'fetch' => PDO::FETCH_CLASS,
'default' => env('DB_DRIVER', 'mysql'),
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'database' => storage_path() . '/database.sqlite',
'prefix' => '',
],
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', 'localhost'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'strict' => false,
],
],
];
Your .env
file will simply contain the parameters that change from deployment to deployment.
DB_HOST=localhost
DB_DATABASE=mydatabase
DB_USERNAME=myusername
DB_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.
<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
</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.
function envswitch() {
if [ ! "$1" ]
then
echo "Missing required parameter envname"
echo "File should exist in current directory as .env.envname"
echo "Usage:"
echo " envswitch envname"
return
fi
ENV_PATH="./.env"
ENV_LINK=".env.$1"
# Ensure the file being sylinked exists
if [ ! -e "$ENV_LINK" ]
then
echo "Error: "$ENV_LINK" does not exist in current directory"
return
fi
# If the ENV_PATH already exists, remove it
if [ -e "$ENV_PATH" ]
then
rm -f "$ENV_PATH"
fi
# Symlink the file to $ENV_LINK
ln -s "$ENV_LINK" "$ENV_PATH"
}
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.