Pull to refresh

Кэшируем модели

Reading time3 min
Views933
Совсем недавно стала задача организовать кэширование данных. И получилась весьма удобная вещь. Реализацию смотри под хабракатом…

Описанный ниже абстрактный класс кладем к нашим моделям в папку Cache. В моём случае это /Default/Models/Cache/Abstract.php

abstract class Default_Models_Cache_Abstract
{
    protected static $_cache;
    protected static $_staticCache;
    protected static $_tags = null;
    protected static $_className = null;
    protected static $_nonCachedMethods = array();

    protected static $_frontendName;
    protected static $_backendName;
    protected static $_frontendOptions = array();
    protected static $_backendOptions = array();

    public function __construct()
    {
        static::$_cache = null;
        static::$_staticCache = null;
        static::_init();
    }

    public function __call($name, $arguments)
    {
        return static::_call($name, $arguments);
    }

    public static function __callStatic($name, $arguments)
    {
        return static::_call($name, $arguments, true);
    }

    protected static function _call($name, $arguments, $useStatic = false)
    {
        static::_init($useStatic);
        $cache = $useStatic ? static::$_staticCache : static::$_cache;
        if (in_array($name, static::$_nonCachedMethods)) {
            return call_user_func_array(array($cache, $name), $arguments);
        }
        $id = $cache->makeId($name, $arguments);
        if (!($result = $cache->load($id))) {
            $result = $cache->__call($name, $arguments);
            $cache->save($result, $id, static::$_tags);
        }
        return $result;
    }

    protected static function _initConfig()
    {
        static $inited = null;
        if (null === $inited) {
            if (null === static::$_className) {
                throw new Exception("You must provide a <code>protected static"
                    . " \$_className = ...;</code> statement in your class!");
            }
            static::$_backendName = 'File';
            static::$_frontendName = 'class';
            static::$_frontendOptions = array();
            static::$_backendOptions = array();
            $inited = true;
        }
    }

    protected static function _init($useStatic = false)
    {
        $ref = null;
        if ($useStatic && null === static::$_staticCache) {
            $ref = static::$_className;
        } elseif (null === static::$_cache) {
            if (!class_exists(static::$_className)) {
                require_once 'Zend/Loader.php';
                Zend_Loader::loadClass(static::$_className);
            }
            $ref = new static::$_className;
        }
        if (null !== $ref) {
            static::_initConfig();
            static::$_tags = array(static::$_className);
            static::$_frontendOptions['cached_entity'] = $ref;
            if (!empty(static::$_nonCachedMethods)) {
                static::$_frontendOptions['non_cached_methods']
                    = is_array(static::$_nonCachedMethods)
                        ? static::$_nonCachedMethods
                        : array(static::$_nonCachedMethods);
            }
            $cache = Zend_Cache::factory(
                static::$_frontendName,
                static::$_backendName,
                static::$_frontendOptions,
                static::$_backendOptions
            );
            if ($useStatic) {
                static::$_staticCache = $cache;
            } else {
                static::$_cache = $cache;
            }
        }
    }

    public function cleanCache()
    {
        $ids = static::$_cache->getIdsMatchingTags();
        foreach($ids as $id) {
            static::$_cache->remove($id);
        }
        static::$_cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, static::$_tags);
    }
}

Для работы необходимо иметь рабочую модель, например Default_Models_Products.
В папке Cache, относительно файла нашей модели, создаем класс Default_Models_Cache_Products со следующими свойствами $_className и $_nonCachedMethods:
class Default_Models_Cache_Products extends Default_Models_Cache_Abstract
{
    protected static $_className = 'Default_Models_Products';
    protected static $_nonCachedMethods = array('addProduct');
}

В $_className указываем имя кэшируемого класса. $_nonCachedMethods не обязательное свойство, в нем мы перечисляем те методы, которые нам кэшировать не нужно.

Все обращения к классу Default_Models_Products заменим на Default_Models_Cache_Products, работаем с ним также как и с Default_Models_Products.
Например, в нашей моделе реализован метод getList.
class Default_Models_Products
{
    ...
    public function getList()
    {
        ...
        return $list;
    }
    ...
}

Получить данные мы можем следующим образом:
$productsModel = new Default_Models_Cache_Products();
$productList = $productsModel->getList();

Реализована также возможность вызова статических методов. Если метод getList является статическим:
class Default_Models_Products
{
    ...
    public static function getList()
    {
        ...
        return $list;
    }
    ...
}

вызвать его можно будет так:
$productList = Default_Models_Cache_Products::getList();

Для очистки кэша реализован метод cleanCache(), при его вызове будет произведена очистка кэша, относящегося только к данной моделе, что может оказаться весьма полезным.

Про задание параметров $_frontendOptions и $_backendOptions можно почитать тут и тут

Получившийся класс весьма универсален и подойдет не только для моделей.

P.S. Уточнение. Спасибо Виктору(Yeah) за подсказку, этот класс не будет работать в версии php ниже 5.3. Поскольку магический метод __callStatic() появился именно с этой версией php.
Tags:
Hubs:
Total votes 23: ↑15 and ↓8+7
Comments15

Articles