Lithium - Diving into the core, Environment

The Environment class is a core, static object which will inspect and determine the environment based on certain criteria from the current request. One of the neatest bits of functionality it gives us is that it will automatically configure any Adaptable class based on the current environment. This lets us do things like:

Cache::config(array(
    'userData' => array(
        'development' => array('adapter' => 'File'),
        'production' => array('adapter' => 'Memcache')
    )
));

Now, depending on the environment, when we attempt to use the Cache class against the “userData” configuration it will automatically use the appropriate environment.

Determining environment

While this is all well and good, how does the Environment class determine the current environment in the first place? Looking at the class we don’t see anything which is automatically triggered to actually determine which environment we’re in. That’s because there is none. It needs to be told about the current request.

If you’re using the framework repository as your basis (which you should be!) then the file you want to look at is app/config/bootstrap/action.php

Dispatcher::applyFilter('run', function($self, $params, $chain) {
    Environment::set($params['request']);

    foreach (array_reverse(Libraries::get()) as $name => $config) {
        if ($name === 'lithium') {
            continue;
        }
        $file = "{$config['path']}/config/routes.php";
        file_exists($file) ? include $file : null;
    }
    return $chain->next($self, $params, $chain);
});

The line in question is Environment::set($params['request']) which grabs the Request object from the Dispatcher and throws it at the class. From there the class will trigger the _detector method and figure out the current environment.

One thing to notice, and it’s a big one, is that your Environment object will not know where it “is” until this filter executes which means waiting for the Dispatcher::run method to fire. This also means that using Environment anywhere in your bootstrap, without being wrapped in a filter that comes after Dispatcher::run, will not give you the desired results.

Environment detector

When Environment::set gets the Request object it turns around and fires it at its Environment::_detector method which handles the dirty work of determining the environment and returning the name.

Inside _detector you’ll see that it checks for a static member variable called $_detector (more on that later) before returning its own, built-in, closure to handle determining the environment.

Custom detector

Now that we’ve got that out of the way, let’s see how we can define our own detector for the Environment class to use. This will let us create new environments or add criteria for choosing an environment.

Remember earlier when we mentioned the static member variable, $_detector? Well Lithium lets us override the built-in closure for determining the environment with our own closure by setting this variable. Here’s how…

Environment::is(function($request) {

    /* Determine which environment and return as string */

    // Always be sure to fall back onto "production"    
    return 'production';
});

This will store away our closure into the object for use when it receives the Request object (like, during the Dispatcher::run filter above) — always, always, always be sure that your closure returns a value of some sort. The value can be any string you want but it will become the name of the current environment.

Since the call to Environment::set is responsible for setting our current environment you’ll need to set your custom detector before it is triggered, otherwise it won’t end up being used.

Another note is to remember that this call is destructive and not additive. You won’t receive any of the built-in checks if you add your own detector.

Arbitrary environment specific storage

This is actually the easiest part and is fairly well covered in the documentation itself but while we’re here, a quick example.

Environment::set('production', array('debug' => false));
Environment::set('development', array('debug' => true));

Later, performing Environment::get('debug') will give us the corresponding value depending on the environment that was detected. The get method also allows us to use a convenient dot notation for getting at nested values.

Closing

Hopefully this has been a fairly clear outline of the awesome Environment class and a bit of its inner workings. If you have any questions, feel free to ask!