Кеширование по тегам для Yii 1.*
Так как в Yii кеширование довольно развито, можно задаться вопросами: "Зачем изобретать велосипед?" и "не проще ли использовать предлагаемые фреймверком решения?". Все это верно, только в том контексте, когда нам нужно простое кеширование. Например, страница одной новости. Но когда у нас большой проект, то при реализации кеширования возникают некоторые трудности.
Допустим, для конкретного товара нужно кешировать его характеристики, фото, обзоры, фильтра, аксессуары. И хочется, чтобы при изменении чего-то одного, обновление кеша не затрагивало остальное (при загрузке дополнительного фото перекеширование характеристик, фильтра, обзоров и аксессуаров нелогично и затратно). Конечно можно добавить в кеш каждый компонент отдельно и не связывать его с товаром. Но тогда мы сталкиваемся с проблемой размножения мелких компонентов, принадлежащих к одному объекту и никак несвязанных. И в случае удаления товара, сброс кеша для всех этих компонентов будет занимать дополнительные ресурсы сервера из-за того, что сначала нужно найти их связь с товаром. Опять же, можно где-то хранить массив связей товара с его компонентами (в том же кеше под определенным id). Тогда получается не только лишний посредник, но и не совсем истетическое решение проблемы. Более простой и изящный способ - это кеширование по тегам, где записи кеша будут зависить от тега (например, продукт) или группы тегов. Именно такое решение я и представлю ниже.
Для начала создадим компонент (components/TagDependency.php):
class TagDependency implements ICacheDependency
{
const PREFIX = '___tag___';
/**
* Временная метка.
*
* @var integer
*/
protected $timestamp;
/**
* Список тегов.
*
* @var array
*/
protected $tags;
/**
* @param string|array $tags
*/
public function __construct($tags)
{
$this->tags = (array) $tags;
}
public function evaluateDependency()
{
$this->timestamp = time();
return true;
}
public function getHasChanged()
{
$tags = array_map(create_function('$e', 'return TagDependency::PREFIX . $e;'), $this->tags);
$tagsTime = Yii::app()->cache->mget($tags);
foreach ($tagsTime as $time)
if ($time >= $this->timestamp)
return true;
return false;
}
}
Далее для удобства создаем хелпер (helpers/CacheHelper.php):
class CacheHelper
{
/**
* Обновляем время у тегов делая кеш невалидным.
*
* @param string|array $tags
*/
static public function clearTags($tags)
{
foreach ((array) $tags as $tag)
Yii::app()->cache->set(TagDependency::PREFIX . $tag, time());
}
/**
* Удаляем теги.
*
* @param string|array $tags
*/
static public function deleteTags($tags)
{
foreach ((array) $tags as $tag)
Yii::app()->cache->delete(TagDependency::PREFIX . $tag);
}
}
Теперь давайте посмотрим на результат нашей работы:
// Возьмем текущий класс через который осуществляется кеширование
$cache = Yii::app()->cache;
// Сохраним фильтра категории1 в кеш с указанием зависимостей от тегов
$cache->set('category.1.filters', 'data', 0, new TagDependency(array('product-1', 'product-2')));
// Смотрим, что запись в кеше имеется
var_dump($cache->get('category.1.filters')); // data
// Сохраним фото товара1 в кеш с указанием зависимости от товара1
$cache->set('product.1.fotos', 'product-1-fotos-data', 0, new TagDependency('product-1'));
// Смотрим, что запись в кеше имеется
var_dump($cache->get('product.1.fotos')); // product-1-fotos-data
// Сохраним фильтра товара1 в кеш с указанием зависимости от товара1
$cache->set('product.1.filters', 'product-1-filters-data', 0, new TagDependency('product-1'));
// Смотрим, что запись в кеше имеется
var_dump($cache->get('product.1.filters')); // product-1-filters-data
// Сохраним фото товара2 в кеш с указанием зависимости от товара2
$cache->set('product.2.fotos', 'product-2-fotos-data', 0, new TagDependency('product-2'));
// Смотрим, что запись в кеше имеется
var_dump($cache->get('product.2.fotos')); // product-2-fotos-data
// Ждем секундочку..
sleep(1);
// Сбрасываем кеш для тега `product-1`
CacheHelper::clearTags('product-1');
// Т.к. фильтра категории1, фото и фильтр продукта1
// зависят от тега `product-1`, то при очистке тега
// их тоже не будет существовать
var_dump($cache->get('product.1')); // false
var_dump($cache->get('category.1.filters')); // false
var_dump($cache->get('product.1.fotos')); // false
var_dump($cache->get('product.1.filters')); // false
// просто для наглядности, проверим, что
// фото товара2 еще в кеше
var_dump($cache->get('product.2.fotos')); // product-2-fotos-data
// удалим теги
CacheHelper::deleteTags(array('product-1', 'product-2'));
Таким образом у нас получилось построить зависимость фильтров и фотографий от товара. Конечно же можно использовать не только связку товар-компоненты товара, но и любые другие связи. И независимо от того, что мы добавим в кеш, записи будут храниться структуировано и логично. А удаление всех зависимостей будет занимать одну строчку кода.