Cache’owanie konfiguracji aplikacji opartej o Zend Framework

Duże aplikacje napisane w oparciu o Zend Framework zazwyczaj zawierają duży plik konfiguracyjny, niejednokrotnie zapisany w postaci XML. Im większy plik konfiguracyjny, tym wolniej jest on tłumaczony do postaci rozumianej przez PHP. Z tego właśnie względu warto zastosować cache’owanie konfiguracji naszej aplikacji.

Wbrew pozorom nie jest to proces skomplikowany i wymaga jedynie kosmetycznych poprawek w projekcie. Wystarczy utworzyć klasę dziedziczącą po Zend_Application, a w niej przesłonić metodę odpowiedzialną za wczytanie pliku konfiguracyjnego.

class Batman_Application extends Zend_Application
{
    /**
     * @var null|Zend_Cache_Core
     */
    private $_cache = null;

    /**
     * @var null|string
     */
    private $_configFile = null;

    /**
     * @param string $file
     */
    public function setConfigFile($file)
    {
        $this->_configFile = $file;
    }

    /**
     * @return null|string
     */
    public function getConfigFile()
    {
        if($this->_configFile === null) {
            $this->_configFile = APPLICATION_PATH . '/configs/application.ini';
        }
        return $this->_configFile;
    }

    /**
     * @param Zend_Cache_Core $cache
     */
    public function setCache(Zend_Cache_Core $cache)
    {
        $this->_cache = $cache;
    }

    /**
     * @return null|Zend_Cache_Core
     */
    public function getCache()
    {
        if($this->_cache === null) {
            $this->_cache = Zend_Cache::factory(
                'File',
                'File',
                array(
                    'master_files' => array($this->getConfigFile()),
                    'automatic_serialization' => true,
                    'lifetime' => null
                ),
                array(
                    'cache_dir' => APPLICATION_PATH . '/data/cache'
                )
            );
        }
        return $this->_cache;
    }

    protected function _loadConfig($file)
    {
        if($this->_configFile === null) {
            $this->setConfigFile($file);
        }

        $cache = $this->getCache();
        $config = $cache->load('config_cache');
        if(!$config) {
            $config = parent::_loadConfig($file);
            $cache->save($config, 'config_cache');
        }

        return $config;
    }
}

Powyższy przykład korzysta z cache’u File, który automatycznie się odświeża, gdy w pliku konfiguracyjnym zostaną wprowadzone zmiany. Nic nie stoi na przeszkodzie aby to zmienić na dowolny inny cache, podobnie z resztą jak backend. Jeśli mamy dostęp do Memcached lub APC, o wiele lepiej będzie tam przechowywać cache, niż w pliku.

Jedyną zmianę jaką należy wprowadzić w już istniejącym kodzie, to modyfikacja pliku index.php, w którym zmieniamy nazwę klasy Zend_Application, na klasę przez nas utworzoną.

// reszta pliku index.php

require_once 'Zend/Application.php';
require_once 'Batman/Application.php';

// Create application, bootstrap, and run
$application = new Batman_Application(
    APPLICATION_ENV,
    APPLICATION_PATH . '/configs/application.ini'
);
$application->bootstrap()
            ->run();

Zanim jednak zdecydujemy się na cache’owanie konfiguracji, lepiej upewnić się, że taka zmiana nam się opłaci. Może się bowiem tak zdarzyć, iż parsowanie pliku z konfiguracją jest szybsze niż sprawdzenie czy cache jest aktualny.

Pliki z kodem źródłowym znajdziecie na Githubie.

Aktualizacja

W komentarzach melkrom słusznie zauważył, iż plik konfiguracyjny powinien być pobierany ze zmiennej przekazanej do metody _loadConfig. Dodałem taką możliwość do klasy. Wpis oraz projekt na Githubie zostały zaktualizowane do nowej wersji.

  1. Zgadzam się w 100%.
    W projekcie nad którym pracuję, parsowanie konfiguracji aplikacji zajmowało ponad 10% czasu wykonania skryptu. Przerobienie aplikacji, aby config ten cachowany był w APC zajmuje pare minut.
    Jak widać stosunek przyrostu wydajności, do nakładu pracy jest olbrzymi, dlatego warto się tym zainteresować.

    BTW Trzeba też pamiętać o flush’owaniu cache’u przy deploy’u applikacji.

  2. Ogólnie wolałbym skorzystać z cache managera zendowego – i zrobić to w zewnętrznej klasie by móc jej używać wszędzie i dla innych plików konfiguracyjnych – takie podejście jest oczywiście spoko ale przyczepię się do jednej rzeczy

    'master_files' => array(APPLICATION_PATH . '/configs/application.ini'),

    Skąd założenie że właśnie tak trzymam plik konfiguracyjny? :]

  3. @Kamil Nowak
    Usunięciem cache’u zajmuje się zazwyczaj skrypt do deploy’u aplikacji, więc na szczęście nie trzeba sobie tym głowy zawracać :)

    @melkrom
    Lokalizację pliku konfiguracyjnego przyjąłem domyślną, generowaną przez framework. Jeśli plik jest zlokalizowany w innym miejscu lub chcemy skorzystać z innego cache’u, wystarczy skorzystać z metody setCache (tutaj index.php nieco spuchnie).

  4. @melkrom

    Ponieważ tak masz w index.php. Jak sobie tam zmieniłes to musisz pamiętać, żeby gdziekolwiek indziej zmieniac i/lub dac to w jakąś zmienną.

  5. @SebaZ
    Nie muszę – ponieważ plik konfiguracyjny ładujesz raz, defaultowo w index.php.

    @batman, a czy w metodzie getCache nie lepiej by było dać ścieżkę do pliku z _loadCache i byłoby po sprawie :) ?

  6. @melkrom
    W sumie racja. Zmienię to pod wieczór i uaktualnię wpis.

  7. Jedna mała uwaga. W testach w testSetInvalidDataTemplate zamiast bloku try catch powinno być: $this->setExpectedException(‘Batman_Notification_Exception’);

    Pozdrawiam

  8. @melkrom
    Wpis (i klasa) zaktualizowany.

    @Darek
    Niestety nie mam teraz jak tego sprawdzić, ale jestem prawie pewien,że zdecydowałem się na takie coś z jakiegoś konkretnego powodu.

  9. Fajny pomysł na przyspieszenie, ale wydaje mi się, że kopa dostanie jeśli cache przeniesiemy do pamięci i wyłączymy sprawdzanie ważności (np: na produkcji).

Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Subscribe without commenting