Caching of Zend Framework application configuration file

May 26, 2011

Uncategorized

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.

11 Responses to “Caching of Zend Framework application configuration file”

  1. dasprid Says:

    Or you could just use PHP config files which are then cached by an opcode cache like APC ;)

  2. toma Says:

    My 20 lines don’t take into account the environment as part of the ini cache identifier, which the author does.

    if (!$config = $cache->load(APPLICATION_ENV . ‘_applicationIni’)) {

    will fix my error.

  3. toma Says:

    I think you had a novel idea and you shared it clearly in this article but here is the same thing in 20 lines of code:

    $iniFile = APPLICATION_PATH . ‘/configs/application.ini’;

    require_once ‘Zend/Cache.php’;
    $cache = Zend_Cache::factory(‘File’, ‘File’, array(
    ‘master_files’ => array($iniFile),
    ‘automatic_serialization’ => true
    ), array (
    ‘cache_dir’ => APPLICATION_PATH . ‘/../data/cache/’
    ));

    if (!$config = $cache->load(‘applicationIni’)) {
    require_once ‘Zend/Config/Ini.php’;
    $config = new Zend_Config_Ini($iniFile, APPLICATION_ENV);
    $config = $config->toArray();
    $cache->save($config);
    }

    require_once ‘Zend/Application.php’;
    $application = new Zend_Application( APPLICATION_ENV, $config);
    $application->bootstrap()->run();

  4. dreamer111 Says:

    I’ve never seen much user from using anything except standard PHP arrays for config files.
    Also – this makes them completely natively cacheable by opcode cache.

    there is also an article about it by some good chump:
    http://www.dasprids.de/blog/2009/05/08/writing-powerful-and-easy-config-files-with-php-arrays

  5. alokin Says:

    Totally agree… Usually, my ZF-based projects have bulky configurations, and for one of those, I had a demand for optimizing that segment of a application. Then I came to idea for creating this custom Zend_Application class, enriched with capability of caching configuration file that it process on every request.

    But also, even lightest app configuration file would have at least 10 lines. Even more, when it comes to XML format, I believe that caching is very desirable, because I think that Zend_Config adapter for XML configuration files is not so reputable in terms of speed and performance.

  6. caaguado Says:

    I don’t doubt this can be useful for projects with big config files, but if you just have a config file with 3 or 4 settings in a .INI file, using this class would most likely make your app run slightly slower… So… the old saying ‘use the right tool for the task’ seems appropiate here…

  7. alokin Says:

    Yes, I agree, but I think that this implementation give you more flexibility, as you can override _loadConfig() method to be public, and than consume its functionality from your Bootstrap, when loading some other config file, for example:
    $this->_application->_loadConfig(‘path/to/some/config.xml’);
    btw That will require some changes in method itself, as $this->_environment will be passed as a section argument when instantiating some Zend_Config_*.

  8. bcharb Says:

    Seems like a good opportunity to use Zend_Cache_Frontend_File actually.

    http://framework.zend.com/manual/en/zend.cache.frontends.html#zend.cache.frontends.file

    In fact, this is the exact example use case they provide in the documentation:

    "Zend_Cache_Frontend_File is a frontend driven by the modification time of a "master file". It’s really interesting for examples in configuration or templates issues. It’s also possible to use multiple master files.

    For instance, you have an XML configuration file which is parsed by a function which returns a "config object" (like with Zend_Config). With Zend_Cache_Frontend_File, you can store the "config object" into cache (to avoid the parsing of the XML config file at each time) but with a sort of strong dependency on the "master file". So, if the XML config file is modified, the cache is immediately invalidated."

  9. alokin Says:

    clearstatcache() should only be used in situations when one file is examined multiple times per one request, as, for example, it can be changed during that execution. That function caches information per one request not between multiple requests.

    Yes, I was doing some basic performance measurements, but I can’t provide any numbers…

  10. chelmertz Says:

    You could probably use <a href="http://www.php.net/manual/en/function.clearstatcache.php">clearstatcache()</a&gt; before filemtime() to be extra safe :)

    Did you do any profiling in your test cases?

    Cheers