Environment-Specific Configuration for CraftCMS Using PHPDotEnv

TL;DR: save your environment-specific configuration details in a git-ignored .env file. Instructions below.

What's wrong with the current system? #

Craft is a fantastic CMS, but every CMS shows some pain points when you have a large team working on the same site at the same time. One of these points for me is Craft's native multi-environment configuration options, which allow you to define configuration options based on the domain name:

return [
    '*' => [
        'omitScriptNameInUrls' => true,
    ],
    'example.dev' => [
        'devMode' => true,
    ],
    'example.com' => [
        'cooldownDuration' => 0,
    ]
];

This is great, but it's limited: You're hard-coding the configuration details into your code, which sometimes means putting sensitive information into your version control. Every developer's local installs either all have to have different domains, or if they use the same domain they need to all have the same configuration settings. And something just feels dirty about the codebase having such knowledge of every place it's going to be deployed.

A better way #

I've fallen in love with how easy dotenv and phpdotenv make it to keep particular variables (e.g. database connection information) unique for each environment (local, staging, production, etc.) without committing them all to version control (e.g. GitHub).

This is especially helpful when you're developing as a part of a team, who may have different connections across each of their unique "local" environments. It also is more secure, because your production database credentials aren't accessible to every person with access to your git repo.

How does phpdotenv work? #

phpdotenv allows you to load in a file named .env that sits in your project root (you can customize where it lives or what it's named, but that's the default) and add its keys/values to your $_ENV global. Here's quick look at a sample .env file:

DB_HOST=localhost
DB_NAME=my_web_site
DB_USER=root
DB_PASS=root

With phpdotenv, all the values you set in .env become accessible across your entire codebase via $_ENV and the getenv() function, so you can have a single file for your environment-specific variables that gets loaded at runtime and makes them accessible anywhere. That means, anywhere in your code, you can just write getenv('DB_HOST') and it will return, in the example above, localhost.

Additionally, you can require that each environment's .env must contain certain fields, so that if a particular environment is missing certain keys, phpdotenv will make a fuss until you fix it.

If you're confused as to how each environment has a unique .env file, it's because you use .gitignore to tell git to ignore that file, and in each environment you'll make a new copy from a template, named .env.example.

How do I add phpdotenv to my Craft site? #

Note: This requires using Composer. That might sound scary, but trust me, it's going to be simple.

1. Initialize a Composer project and require vlucas/phpdotenv #

First, create a file in your project root named composer.json. Fill it with the following:

{
    "require": {
        "vlucas/phpdotenv": "^2.0"
    }
}

If you haven't yet, install Composer.

Run composer install.

2. Add the Composer autoloader and Dotenv loader to index.php #

Edit your public/index.php file and add these lines to the top:

require_once('../vendor/autoload.php');

try {
    $dotenv = new Dotenv\Dotenv(dirname(__DIR__));
    $dotenv->load();
    $dotenv->required(['DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASS']);
} catch (Exception $e) {
    exit('Could not find a .env file.');
}

3. Create .env and .env.example #

Now create a file in the root named .env. For now, fill it with this:

DB_HOST=localhost
DB_NAME=craft
DB_USER=root
DB_PASS=root

Duplicate that file and name the duplicate .env.example.

4. Git ignore environment-specific files #

Add these lines to your .gitignore file:

/vendor/
.env

5. Update .env with appropriate connection details #

Now go into craft/config/db.php (if this is an existing site) and move those values into your .env file so that it looks something like this:

DB_HOST=my.db.server.com
DB_NAME=mysite_craft
DB_USER=mysite_sql_user
DB_PASS=1395h901h91jr91

6. Update Craft config files to get values from the $_ENV #

Update craft/config/db.php to look like this:

return [
    'server' => getenv('DB_HOST'),
    'user' => getenv('DB_USER'),
    'password' => getenv('DB_PASS'),
    'database' => getenv('DB_NAME'),
    'tablePrefix' => 'craft',
];

That's it! Your site is now getting its configuration details from your .env file. Every time you spin up a new instance of this site, just create a new .env file from the .env.example template in the new environment and set its details appropriately.

As you've probably realized, you can set other properties in here; I use it to set BASE_URL and then pull that in craft/config/general.php as an environmentVariable.

What did we just do? #

Composer allows us to pull in external code. So we initialized a new Composer configuration file (if you're a Composer guru, you might be mad that I didn't teach composer init... I know, me too), and then told Composer to require this phpdotenv package. Then we asked Composer to install it.

Then we needed to pull the Composer autoloader into our code so that we had access to any packages it installs. Once we had that, we could use Dotenv's loader to pull in our .env file and import its keys and values to our $_ENV.

We updated our .env to have the correct connection details, and then updated Craft's database configuration array to pull its details from our .env using the getenv() function.

What does this do to my deployment requirements? #

This has two effects on your deployment.

First, every time you spin up a new environment, you need to copy .env.example to .env and fill out those details correctly for the new environment.

Second, your deployment servers all need Composer. Thankfully, every modern host has Composer on it. If you don't have a good host, I highly recommend Laravel Forge and DigitalOcean for quick and easy Craft hosting.

Conclusion #

That's it! Look at you and your environment-specific configs go!

Note: I just added Laravel's env() helper function to my craftPluginDevHelpers plugin. This allows you to set fallback default values, and it also converts boolean values like true to actual PHP booleans. However, relying on an installed plugin for this is a bit sketchy, since if it's not installed your env won't work right. So, I would recommend either manually checking for boolean strings (e.g. if (getenv('THINGISENABLED') == 'true')), OR including Laravel's Illuminate/Support package via Composer.


Comments? I'm @stauffermatt on Twitter


Tags: phpdotenv | craft

Matt Stauffer headshot

Hi, I'm Matt Stauffer.

I'm partner & technical director at Tighten Co.

You can find me on Twitter at @stauffermatt


Like what you're reading?

I wrote an entire 450+ page book for O'Reilly: Laravel: Up and Running.

You can order the eBook or print book today.