Caching of Zend Framework application configuration file

If you think that you've done everything in terms of performance optimization of your Zend Framework-based project, I bet that your application configuration file was not included in that process. That's the one whose path you supply when instantiating Zend_Application in your index.php. It is usually called “application” and it is written in INI format, but it can be, for example, foo.xml as well. Reason why I'm referring to application config is that its parsing is performed on every request, which is certainly unnecessary, as you don't make configuration changes very often. Solution is simple – cache it.

I've seen many procedural-style solutions for achieving that goal, where caching is implemented early in index.php, using combination of, for example, apc_*() functions. I like doing things OOP-way, so I'll rather create custom Zend_Application class, which is extended with capability of caching supplied configuration file. Here is how such class might look like:

class My_Application extends Zend_Application
{
    /**
     *
     * @var Zend_Cache_Core|null
     */
    protected $_configCache;

    public function __construct($environment, $options = null, Zend_Cache_Core $configCache = null)
    {
        $this->_configCache = $configCache;
        parent::__construct($environment, $options);
    }

    protected function _cacheId($file)
    {
        return md5($file . '_' . $this->getEnvironment());
    }

    //Override
    protected function _loadConfig($file)
    {
        $suffix = strtolower(pathinfo($file, PATHINFO_EXTENSION));
        if (
            $this->_configCache === null 
            || $suffix == 'php' 
            || $suffix == 'inc'
        ) { //No need for caching those
            return parent::_loadConfig($file);
        }

        $configMTime = filemtime($file);
        
        $cacheId = $this->_cacheId($file);
        $cacheLastMTime = $this->_configCache->test($cacheId);
        
        if (
            $cacheLastMTime !== false 
            && $configMTime < $cacheLastMTime
        ) { //Valid cache?
            return $this->_configCache->load($cacheId, true);
        } else {
            $config = parent::_loadConfig($file);
            $this->_configCache->save($config, $cacheId, array(), null);

            return $config;
        }
    }
}

As you can see, constructor now accepts third, optional argument, through which cache instance can be injected. And as Zend_Application::_loadConfig() method is in charge for loading configuration files, idea is to override that method, and customize it, so that it contain caching logic, too.

PHP-based config files (those with .php and .inc extension) are not cached, as no heavy operation is performed for their loading. Their content is simply included. Next, file modification time is retrieved using filemtime() function. It will be used for comparing against the last modified time of a cache entry, retrieved using Zend_Cache_Core::test() method. This is done so that some changes in your config file are automatically effective, without needing to clear its cache entry, created by this class.

To utilize this custom Zend_Application class, make appropriate changes in your index.php:

//path constants, app env, include path...

require_once 'My/Application.php';

$configCache = null;
//We will cache only in production environment
if (APPLICATION_ENV == 'production') { 
    require_once 'Zend/Cache/Core.php';
    require_once 'Zend/Cache/Backend/Apc.php';
    $configCache = new Zend_Cache_Core(array('automatic_serialization'=>true));
    $backend = new Zend_Cache_Backend_Apc();
    $configCache->setBackend($backend);
}

$application = new My_Application(
    APPLICATION_ENV,
    '/path/to/app_config.ini',
    $configCache
);
$application->bootstrap()->run();

Note that our custom class will not be caching configuration files if $_configCache is not set. In this example, I'm creating cache instance only if we're in production environment. Also note that I've used Apc on backend, but of course, you can use any of supported backends.

Once you have implemented caching for your SQL queries, pages, and other stuff, boost your performance little more, by doing the same for your application configuration.