Кэширование данных заключается в сохранении некоторой переменной PHP в кэше и её последующем извлечении. Оно является основой для расширенных возможностей, таких как кэширование запросов и кэширование страниц.
Кэширование данных (Полное руководство по Yii 2.0) здесь
Abstract Class yii\caching\Cache
(API Documentation) здесь
По-умолчанию кэш в Yii2 (Advanced
) разделён и сохраняется в соответствующие каталоги: backend\runtime\cache
и frontend\runtime\cache
. Убедиться в этом можно, вызвав команду Yii::$app->cache
из frontend или backend, соответственно.
Для того, чтобы иметь возможность работать с кэшем другого приложения (например из backend удалить кэш frontend) можно указать для этих приложений общее хранилище. Весь кэш приложений будем хранить в каталоге common
, затем будем удалять нужный нам кэш (в том числе кэш frontend) из админки.
В файле common\config\main.php
:
'components' => [
'cache' => [
'class' => 'yii\caching\FileCache', // Используем хранилище yii\caching\FileCache
'cachePath' => '@common/runtime/cache' // Храним кэш в common/runtime/cache
]
]
В приложении одновременно можно использовать разные способы хранения кэша. Об остальных поддерживаемых хранилищах читайте в документации, я же буду использовать (для примера) только yii\caching\FileCache
.
Кэширование произвольных данных (результаты выборки, данные внутри виджетов и тп.)
// Обращаемся к кэшу приложения
$cache = Yii::$app->cache;
// Формируем ключ
$key = 'category_list';
// Пробуем извлечь категории из кэша.
$categories = $cache->get($key);
if ($categories === false) {
//Если $categories нет в кэше, вычисляем заново
$categories = Category::find()->all();
// Сохраняем значение $categories в кэше по ключу. Данные можно получить в следующий раз.
$cache->set($key, $categories);
}
print_r($categories);
Начиная с версии 2.0.11, компонент кэширования предоставляет метод getOrSet()
, который упрощает код при получении, вычислении и сохранении данных. Приведённый ниже код делает в точности то же самое, что и код в предыдущем примере:
// Обращаемся к кэшу приложения
$cache = Yii::$app->cache;
// Формируем ключ
$key = 'category_list';
// Данный метод возвращает данные либо из кэша, либо из откуда-либо и записывает их в кэш по ключу на 1 час
$categories = $cache->getOrSet($key, function () {
return Category::find()->all();
}, 3600);
print_r($categories);
Кэширование фрагментов
Кэширование фрагментов относится к кэшированию фрагментов страницы. Обычно кэширование фрагментов используйтся в представлении:
<div class="site-index">
<?php if($this->beginCache('category_list', ['duration' => 3600])):?>
<? foreach($categories as $category): ?>
<p><?= $category->name ?></p>
<? endforeach; ?>
<? $this->endCache(); endif; ?>
</div>
Срок хранения duration
. По умолчанию, если срок хранения не указан $defaultDuration = 0
. Это означает то, что время хранения бесконечен.
// Кэшированние содержимого на бесконечный срок. Если нужен срок хранения кэша, то установить нужное значение в секундах (например, 3600 = 1 час)
if ($this->beginCache($id, ['duration' => 0])) {
// ... здесь содержимое ...
$this->endCache();
}
Зависимости dependency
:
// Отображение содержимого зависит от того, изменена или нет категория (изменения значения столбца updated_at)
$dependency = [
'class' => 'yii\caching\DbDependency',
'sql' => 'SELECT MAX(updated_at) FROM category',
];
if ($this->beginCache($id, ['dependency' => $dependency])) {
// ... здесь содержимое ...
$this->endCache();
}
Вариации variations
на примере кэширования виджета категорий:
<? //В variations нужно передать id текущей категории (для каждой категории менюшка сохраняется в отдельный параметр кэша) ?>
<?php if($this->beginCache('aside-widget', [
'duration' => 3600, //(default = 0)
'dependency' => [
'class' => 'yii\caching\DbDependency',
'sql' => 'SELECT MAX(update_at) FROM ' . Category::tableName(),
],
'variations' => [
isset($this->params['category']) ? $this->params['category']->id : null
]
])): ?>
<?= CategoryWidget::widget() ?>
<?php $this->endCache(); endif; ?>
Кэширование запросов
Кэширование запросов - это специальная функция, построенная на основе кэширования данных. Она предназначена для кэширования результатов запросов к базе данных.
// Кэширование запросов для DAO
// Возвращает ассоциативный массив с именами столбцов и значений
$categories = Yii::$app->db->cache(function () {
return Yii::$app->db->createCommand('SELECT * FROM `category`')->queryAll();
});
// Кэширование запросов для ActiveRecord (на 1 час)
// Возвращает объект
$categories = Category::getDb()->cache(function (){
return Category::find()->all();
}, 3600);
В пределах cache()
вы можете отключить кэширование запроса. В этом случае вы можете использовать [[yii\db\Connection::noCache()]]
:
categories = Yii::$app->db->cache(function () {
// SQL запросы, которые используют кэширование
Yii::$app->db->noCache(function ($db) {
// SQL запросы, которые не используют кэширование
});
return $categories;
});
Если вы просто хотите использовать кэширование для одного запроса, вы можете вызвать [[yii\db\Command::cache()]]
при построении команды:
// использовать кэширование запросов и установить срок действия кэша на 1 час
$categories = $db->createCommand('SELECT * FROM category)->cache(3600)->queryAll();
Можете использовать [[yii\db\Command::noCache()]]
для отключения кэширования запросов для одной команды:
$result = $db->cache(function ($db) {
// ... Используется кэширование SQL запросов ...
// не использовать кэширование запросов для этой команды
$categories = $db->createCommand('SELECT * FROM category)->noCache()->queryAll();
// ...
return $result;
});
Кэширование страниц
Кэширование страниц — это кэширование всего содержимого страницы на стороне сервера. Когда эта страница будет запрошена, сервер вернет её из кэша вместо того чтобы генерировать её заново.
public function behaviors()
{
return [
[
'class' => 'yii\filters\PageCache',
'only' => ['view'],
'duration' => 3600,
'variations' => [
Yii::$app->language,
],
'dependency' => [
'class' => 'yii\caching\DbDependency',
'sql' => 'SELECT MAX(update_at) FROM ' . Page::tableName(),
]
]
];
}
Приведённый код задействует кэширование только для действия view
. Содержимое страницы кэшируется максимум на 1 час и варьируется в зависимости от текущего языка приложения. Кэшированная страница должна быть признана просроченной, если поле update_at
изменилось.
Кэширование страниц очень похоже на кэширование фрагментов. В обоих случаях поддерживаются параметры duration
(продолжительность), dependencies
(зависимости), variations
(вариации), и enabled
(включен). Главное отличие заключается в том, что кэширование страницы реализовано в виде фильтра действия, а кэширование фрагмента в виде виджета.
Очистка кэша
Для очистки всего кэша, вы можете вызвать [[yii\caching\Cache::flush()]]
.
Очистить кэш из консоли можно вызвав yii cache/flush
.
yii cache
: отображает список доступных кэширующих компонентов приложенияyii cache/flush cache1 cache2
: очищает кэш в компонентах cache1, cache2 (можно передать несколько названий компонентов кэширования, разделяя их пробелом)yii cache/flush-all
: очищает кэш во всех кэширующих компонентах приложения
Внимание!
Консольное приложение использует отдельный конфигурационный файл по умолчанию. Для получения должного результата, убедитесь, что в конфигурациях консольного и веб-приложения у вас одинаковые компоненты кэширования.
Очистить кэш из контроллера:
// Очитит кэш всего приложения
Yii::$app->cache->flush();
Очистить кэш фрагмента по ключу:
function deleteFragmentCacheByKey($key)
{
return Yii::$app->cache->delete(['yii\widgets\FragmentCache', $key]);
}
Автоматическое удаление кэша при обновлении записи в базе данных
Когда в БД вносятся изменения (добавление, редактирование, удалении записи в админке), данные в кэше становятся неактуальными. В этом случае можно к модели, которая отвечает за нужную таблицу подключить поведение с событиями для ActiveRecord (EVENT_AFTER_INSERT
, EVENT_AFTER_UPDATE
, EVENT_AFTER_DELETE
), которые при каждом срабатывании будут удалять нужный кэш.
файл CachedBehavior.php
в папке common\components\behaviors
:
<?php
namespace common\components\behaviors;
use Yii;
use yii\base\Behavior;
use yii\db\ActiveRecord;
class CachedBehavior extends Behavior
{
public $cache_key;
public function events()
{
return [
ActiveRecord::EVENT_AFTER_INSERT => 'deleteCache',
ActiveRecord::EVENT_AFTER_UPDATE => 'deleteCache',
ActiveRecord::EVENT_AFTER_DELETE => 'deleteCache',
];
}
public function deleteCache()
{
foreach ($this->cache_key as $id){
Yii::$app->cache->delete($id);
}
}
}
Подключаем поведение к нужной модели:
public function behaviors()
{
return [
'CachedBehavior' => [
'class' => CachedBehavior::class,
'cache_key' => ['category_list'],
]
];
}
Автоматическое удаление кэша при выполнении действий контроллера
Код, указанный выше обновляет кэш при действиях, указаных в поведении CachedBehavior
. Данное поведение привязано к модели, которая в свою очередь занимается ещё выборкой записей. Такой подход в данном случае не очень интнресный, поэтому мы воспользуемся методом удаления кэша из контроллера. Плюсом такого метода является то, что мы можем на нужный нам контроллер навесить нужные события и в конечном итоге модель будет заниматься только логикой (выборка данных и тд), а контроллер по событию каких-либо действий с записями удалять нужный кэш.
Файл common\components\behaviors\DeleteCacheBehavior.php
:
<?php
namespace common\components\behaviors;
use Yii;
use yii\base\Behavior;
use yii\web\Controller;
class DeleteCacheBehavior extends Behavior {
public $cache_key;
public $actions;
public function events()
{
return [
Controller::EVENT_BEFORE_ACTION => 'deleteCache',
];
}
public function deleteCache()
{
$action = Yii::$app->controller->action->id; //название текущего действия
if(array_search($action, $this->actions)=== false) return;
Foreach ($this->cache_key as $id){
Yii::$app->cache->delete($id);
}
}
}
Добавляем поведение в контроллер (у меня это backend\controllers\CategoryController.php
):
public function behaviors()
{
return [
// ...
//Класс удаление кэша при выполнении указанных действий
[
'class' => DeleteCacheBehavior::class,
'cache_key' => ['category_list'],
'actions' => ['create', 'update', 'delete'],
],
// ...
];
}
Удаление кэша по тэгу
Class yii\caching\TagDependency
(API Documentation) здесь
При кэшировании данных мы указываем зависимость:
$cache = Yii::$app->cache;
$key = 'category_list';
$categories = $cache->getOrSet($key, function () {
return Category::find()->all();
}, 3600, new TagDependency([
'tags' => 'category_list'
]));
В нужном нам контроллере и нужном экшене (после сохранения модели):
if ($model->load(Yii::$app->request->post()) && $model->save()) {
// При обновлении записи обновится кэш по указанному тэгу
TagDependency::invalidate(Yii::$app->cache, 'category_list');
return $this->redirect(['view', 'id' => $model->id]);
}
На гитхабе нашёл вот такой trait. Сам не пробовал использовать. Кому интересно, посмотрите.
Простой сервис для удаления кэша по тэгу
Пример очень простого сервиса по по очистке кэша по тэгу. Дело в том, что лучше бы не вызывать постоянно $cache = Yii::$app->cache;
, а работать с объектом кэша напрямую через некий сервис, который будет в нужный нам момент очищать кэш по тэгу.
<?php
namespace core\services;
use yii\caching\Cache;
use yii\caching\TagDependency;
class CacheService
{
public $cache;
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
public function deleteTag($tag)
{
TagDependency::invalidate($this->cache, $tag);
}
public function flush()
{
$this->cache->flush();
}
}
Там где получаем данные (модель или лучше отдельный репозиторий для получения данных):
<?php
namespace core\readModels\Blog;
use core\entities\Blog\Category;
use core\services\CacheService;
use yii\caching\TagDependency;
class CategoryReadRepository
{
private $cacheService;
public function __construct(CacheService $cacheService)
{
$this->cacheService = $cacheService;
}
public function getAll(): array
{
$key = Category::CACHE_ASIDE;
$query = Category::find()->select(['name', 'slug'])->orderBy(['name' => SORT_ASC]);
return $this->cacheService->cache->getOrSet($key, function () use ($query) {
return $query->all();
}, 0, new TagDependency([
'tags' => Category::CACHE_ASIDE
]));
}
}
Обертка для кэширования данных в Yii2
Можно кэширование обернуть в функцию и избавить себя от многократного писания переменных $data, $cache, $key. Тем более, если рядом используется кэширование нескольких фрагментов данных.
<?php
namespace common\components;
use Yii;
use yii\helpers\Url;
use yii\helpers\Html;
class Controller extends \yii\web\Controller
{
/**
* Data caching
* @param string $cacheId Cache ID
* @param \Closure $query Data to cache
* @param \yii\caching\Dependency $dependency Cache dependency
* @param int $cacheTime Duration in seconds
* @return mixed Output cached model data
*/
protected function cache($cacheId, \Closure $query, \yii\caching\Dependency $dependency = null, $cacheTime = null)
{
$cacheTime = ($cacheTime === null) ? Yii::$app->option->get('cache_time') : $cacheTime;
$data = Yii::$app->cache->get($cacheId);
if ($data === false) {
$data = $query();
Yii::$app->cache->set($cacheId, $data, $cacheTime, $dependency);
}
return $data;
}
}
?>
// === Использование
<?php
namespace frontend\controllers;
use Yii;
use yii\helpers\Html;
use yii\caching\DbDependency;
use yii\web\NotFoundHttpException;
use frontend\models\News\News;
class NewsController extends \frontend\components\Controller
{
/**
* Show single news item
* @param string $title News url
* @return \yii\web\View
* @throws \yii\web\NotFoundHttpException
*/
public function actionShow($title)
{
$title = Html::encode($title);
$news = $this->cache(
'news_' . Yii::$app->language . '_t' . md5($title),
function() use ($title) {
return News::find()
->with(['newsTranslates' => function($query) {
$query->andWhere(['lang' => Yii::$app->language]);
}])
->show($title)
->asArray()
->one();
},
new DbDependency(['sql' => 'SELECT `lastModified` FROM `' . News::tableName() . '` WHERE `alias`="' . $title . '"'])
);
return $this->render('show', compact('news'));
}
}
Здесь я кратким конспектом рассмотрел основные принципы кэширования в Yii2. Всё, о чем не упомянуто здесь, вы можете найти по ссылкам выше.
Комментарии (0)
Пока еще не было комментариев ✍️