Environment-Specific Configuration for CraftCMS Using PHPDotEnv
!Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.
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 liketrue
to actual PHP booleans. However, relying on an installed plugin for this is a bit sketchy, since if it's not installed yourenv
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