Создаем сайт на Lumen (Laravel) - Кеширование
Для корректной работы мультасайтинга необходимо доработать ещё один компонент - КЕШ. В Lumen есть кеширование, но оно одно на все сайты. Мне же нужно сделать свой КЕШ, который будет работать в пределах текущего SID. Для этого создам модуль Cache с singleton-ом с именем SCache. Реализация весьма простая, просто буду перенаправлять все вызовы на обычный Cache, но перед этим добавлять к ключу текущий SID.
<?php
namespace App\Modules\Cache;
use Cache;
use App\SID;
class Singleton implements \App\ISingleton
{
// Получить имя псевдонима
static public function getAlias() { return 'SCache'; }
// Используемое хранилище
protected $store=null;
function __construct($name=null) {
// Используемое хранилище. По умолчанию текущее
$this->store = Cache::store($name);
}
// Преобразовать ключ
protected function _key($key) {
// Добавить к ключу текущий SID
return SID::get().'://'.$key;
}
// Вернуть объект для работы с другим типом КЕШ-а
public function store($name) {
return new Singleton($name);
}
// Перегзуить методы
public function __call( string $name, array $arguments) {
// Первый параметр - это ключ. Преобразовать его
$arguments[0] = $this->_key($arguments[0]);
// Вызвать метод
return call_user_func_array([$this->store,$name],$arguments);
}
}
Добавим в класс метод rememberGet который будет работать аналогично rememberForever за исключением того что для режима отладки он будет всегда вызывать функцию генерации данных. В старой версии сайта у меня такой метод был и активно использовался. Поэтому добавлю его и сюда.
// Получить значение и расчитать его с помощью функции обратного вызова если такого значения нет или это режим отладки
public function rememberGet($key,$callback,$isTransform=true) {
if($isTransform) {
$key = $this->_key($key);
}
$isDebug = config('app.debug');
if( $isDebug || !$this->store->has($key) ) {
$ret = $callback();
if(!$isDebug) {
$this->store->forever($key,self::toSerialize($ret));
}
} else {
$ret = unserialize($this->store->get($key));
}
return $ret;
}
Создам метод для сохранения данных в КЕШ до наступления события. Т.е. данные будут удалены из КЕШ-а при наступлении определенного события. Этим методом можно пользоваться при кешировании запросов в БД. К примеру запрос из БД статьи можно закешировать до события изменения этой статьи. В этом случае будет минимизировано количество запросов в БД. Прототип метода:
rememberEvent($arg1,$arg2,...,$argN,$callback)
Из аргументов $arg1...$argN буду генерировать ключ для кеширования. А метод $callback будет вызываться для генерации данных если их нет в КЕШ-е. Схема работы функции
На схеме видно что сначала данные проверяются в КЕШ-е (2), а затем и в таблице БД (3). Связано это с тем, что некоторые виды КЕШ-а при переполнении могут обнуляться и в этом случае данные будут присутствовать в таблице БД, но отсутствовать в КЕШ-е. В этом случае нужно будет возвращать их из БД без вызова функции генерации данных и сохранять в КЕШ-е.
Также нужно предусмотреть возможность дочерних вызовов. Т.е. внутри функции генерации могут быть вызовы функции rememberEvent. Такой вызов будем называть дочерним. В случае разрушения(очистки) дочернего КЕШа родительский тоже должен быть разрушен(очищен). Поэтому при сохранении нужно учитывать иерархию вида "родительский-дочерние элемент".
Для сохранения данных в таблицу БД будем использовать три таблицы.
Функция генерации данных имеет следующий вид
/**
$arg1,...,$argN -аргументы функции rememberEvent($arg1,...,$argN,$callback)
*/
function callback($arg1,...,$argN) {
...
}
Для получения объекта контроля используется метод SCache::control
SCache::control()->event(...)->event(...)->on(...);
Объект контроля элемента КЕШ-а содержит метод который нужно использовать в функции генерации данных для привязки событий
/**
Привязать событие к элементу КЕШ-а. При наступлении этого события данный элемент КЕШ-а (и все его родители) будет удален
$arg1,...,$argN -аргументы события
*/
public function event($arg1,...,$argN)
Также есть метод в котором можно привязать функции для событий set(установка данных) и remove(удаление данных)
/**
Привязать своих действий к элементу КЕШ-а. При наступлении этого события данный элемент КЕШ-а (и все его родители) будет удален
$arg1,...,$argN -аргументы события
*/
public function on($name/* set или remove */,$fn)
В SCache также создадим методы для генерации событий.
/**
* Начать транзакцию отправки событий
*/
public function sendStart()
/**
* Закончить транзакцию отправки событий
*/
public function sendEnd()
/**
* Отправить сообщение
*/
public function sendEvent(...$arguments)
Метод sendStart начинает транзакцию отправки событий, метод sendEnd заканчивает эту транзакцию отправки событий. Метод sendEven посылает событие. Точнее он отправляет событие в буфер. Событие будет отправлено системе после вызова метода sendEnd. При этом если два раза вызвали sendStart, то отправка событий будет произведена только после второго вызова sendEnd. Такой подход позволит накапливать события и отправлять их в систему одним пакетом. После вызова sendEnd весь КЕШ, который построен на отправленных событиях, будет уничтожен.
Типичный пример кеширования до наступления события (как раз из этого метода сделана картинка лога):
$data = SCache::rememberEvent(123, function ($id) {
SCache::control()->event('test');
$data = SCache::rememberEvent(123, 345, function ($id) {
SCache::control()->event('test2');
return "Валера";
});
//
return 'Диана & ' . $data;
});
И код для информирования системы о произошедшем событии. При его вызове будет сброшен КЕШ, который генерируется кодом выше.
// Начать транзакцию отправки сообщений
SCache::sendStart();
SCache::sendEvent('test1');
SCache::sendEvent('test2');
// Закончить транзакцию и отправить события системе
SCache::sendEnd();
// Данное событие не будет отправлено, так как оно отправляется вне транзакции
SCache::sendEvent('test3');
Так как вызывается событие test2 дочернего элемента КЕШ-а, то будет удален и он и родительский элемент.
Я планирую встроить в модель систему отправки событий изменения атрибутов модели вида
sendEvent('change',<имя класса модели>,<имя поля>)
что позволит вешать события на изменения этих полей. Типичный пример
SCache::sendStart();
$user = Model\User::find(1);
$user->name = 'Валера';
$user->save();
SCache::sendEnd();
В этом случае будет отправлено событие
sendEvent('change',Model\User::class,'name')
Если не делать вызов SCache::sendStart()/SCache::sendEnd() то событие отправлено не будет.