Кеширование с тегами — инструмент, позволяющий точечно обновлять кеш при изменении тех или иных зависимостей.
К сожалению, разработчики Yii не сочли нужным внедрить этот инструмент в ActiveRecord, а стоило бы. Тем не менее, они дали нам возможность сделать это самим.
Реализация привязки тегов к моделям на основе зависимостей уже обсуждалась на хабре habrahabr.ru/post/159079/. Автору отдельная благодарность. Я буду использовать ее как основу, и дополню функциями для автоматической генерации тегов.
Выделим задачи, которые нам придется решить:
Генерация тегов по предопределённым правилам. (стандартизация)
Так как в проекте имеется множество различных сущностей, и связывающие ключи сформированы без определённых стандартов, то нам нужен способ использовать один тег для столбцов, наименование которых отличается. Например, в одной таблице имеем поле user_id, а в другой имеем поле customer_id, оба столбца ссылаются на сущность user. Логично что они должны зависеть от одного и того же тега — user_id.
Автоматическая генерация тегов
Банально перегрузим определенные методы в CActiveRecord.
Теги должны генерироваться для отношений, при выборке нескольких моделей одновременно и при выборке модели по главному ключу (причем главный ключ может быть составным)
Возможность добавить теги вручную
Как бы мы не хотели автоматизировать процесс по максимуму, все таки, найдётся достаточно ситуаций, когда нужно указать теги вручную.
Например, когда выборка модели осуществляется с применением сложного запроса (используется CDbCriteria). Для этого, мы расширим класс CDbCriteria добавив ему свойство $tags, по которому генератор тегов поймет что есть теги добавленные «вручную».
Реализация:
Чтобы удовлетворить все вышеуказанные требования нам нужен функционал позволяющий определить стандарты.
Следующая проблема — это определение какой же тег должен быть удален при изменении определённой модели. При чтении мы создаем/проверяем все теги от которых зависит модель. При записи мы должны удалить только индивидуальные теги. Нельзя удалять теги от которых зависят и другие модели.
Для решения данной проблемы, к сожалению, я был вынужден разделить правила на две группы, соответствующие режимам «чтение» и «запись». Это позволяет нам определить какие теги будут использованы при чтении и какие нужно удалить при записи. Придется вручную определять эти моменты.
Правила будем хранить в методе модели cacheTags($mode='read'). Который должен возвращать массив. Так же, определим, что если cacheTags не объявлен в модели или возвращает пустой массив, то автоматическое кеширование не будет активировано.
Каждый тег будет иметь префикс, состоящий из имени модели в нижнем регистре, т.е. user_id — это тег для свойства id в модели User.
Заходя вперед скажу что, во время реализации пришлось определить несколько видов правил:
- Статический
- Константа
- Ссылка
- Композитный
Статический — линейный элемент массива, значение которого соответствует какому либо столбцу таблицы. Тег будет состоять из префикса и текущего имени правила.
Константа — значение, с лидирующим символом ':', которое будет использоваться вместо имени тега, предварительно удалив символ ':'. Другими словами, в наименование тега не будет добавлен глобальный префикс.
Ссылка — ассоциативный элемент массива, ключ которого соответствует столбцу таблицы, для именования тега будет использоваться значение правила. При этом значение правила может быть представлено в виде других правил.
Композиция — это массив правил. Будет создан соответствующий композитный тег
Пример:
public function cacheTags($mode='read'){
switch ($mode) {
case 'read':
return array(
'id', // статический
'user_id'=>':user_id', // ссылка, при этом значение ссылки представлено в виде константы
array( 'id', 'email'=>'user_email' ) // композитный
);
break;
case 'write':
return array(
'id', // статический
array( 'id', 'email'=>'user_email' ) // композитный
);
break;
}
}
В приведенном примере мы видим, что модель зависит от тегов id, user_id, композитного, состоящего из id и user_email, но при записи будут удалены только теги id и композитный. Таким образом, тег user_id останется не тронутым и другие модели зависящие от него не пострадают.
Все выше перечисленное реализовано в виде библиотеки и выложено на github: github.com/yiix/Cache.
Для установки можете использовать композер или просто скопировать репозиторий. Модели должны наследовать класс YiixCacheTaggingCActiveRecord. Так же не забываем добавить метод cacheTags в каждую модель с описанием правил генерации тегов.
Я не вижу особого смысла загромождать пост кодом из библиотеки, приведу лишь некоторые показательные примеры:
Допустим у нас есть следующие модели:
class User extends YiixCacheTaggedCActiveRecord
{
...
public function cacheTags($mode = 'read')
{
switch ($mode) {
case 'write':
return array(
'id','email','username'=>':user_name',
array('id','email')
);
break;
case 'read':
return array(
'id','email','username'=>':user_name',
array('id','email')
);
default:
break;
}
}
...
}
class Post extends YiixCacheTaggedCActiveRecord
{
...
public function cacheTags($mode = 'read')
{
switch ($mode) {
case 'write':
return array(
'id',
);
break;
case 'read':
return array(
'id',
'authorId'=>':user_id',
);
default:
break;
}
}
...
}
class Tag extends YiixCacheTaggedCActiveRecord
{
...
public function cacheTags($mode = 'read')
{
switch ($mode) {
case 'write':
return array(
'id',
'name',
);
break;
case 'read':
return array(
'id',
'name',
);
default:
break;
}
}
...
}
Логично, что достаточно определить правила для ключевых свойств.
Пример 1.
Тег будет создан по первичному ключу
$model = User::model()->findByPk(1);
$tags = YiixCacheTaggedHelper::generateTags($model);
dump($tags);
результат:
array
(
0 => 'user_id=1'
)
Пример 2.
Теги создаются по данному массиву параметров.
$tags = YiixCacheTaggedHelper::generateTags(User::model(),array(
'id'=>'1',
'email'=>'webmaster@example.com',
'username'=>'demo'
));
dump($tags);
результат:
array
(
0 => 'user_id=1'
1 => 'user_email=webmaster@example.com'
2 => 'user_name=demo',
3 => 'user:user_id=1,user_email=webmaster@example.com'
)
В результате присутствует тег (элемент массива с индексом 3) созданный по композитному правилу.
Пример 3.
Аналогично примеру 2.
$tags = YiixCacheTaggedHelper::generateTags($Tag::model(),array('name'=>'blog'));
dump($tags);
результат:
array
(
0 => 'tag_name=blog'
)
Пример 4.
Добавление тегов вручную.
$criteria = new YiixCacheTaggedCDbCriteria();
$criteria->addInCondition('authorId', array('1','2'));
$criteria->tags = array(
'authorId'=>array('1','2'),
);
$tags = YiixCacheTaggedHelper::generateTags(Post::model(),$criteria);
dump($tags);
результат:
array
(
0 => 'user_id=1'
1 => 'user_id=2'
)
здесь мы видим, что сработало правило «ссылка» и наименование тегов соответствуют сущности User
Эти примеры не будут использоваться напрямую. Они описывают принцип создания тегов, который используется в Yiix/Cache/Tagging/CActiveRecord.
В библиотеке используется пространство имен. Информация по настройке пространства имен описана здесь:
yiiframework.ru/doc/guide/ru/basics.namespace
Спасибо за внимание.
Автор: magicstream