Pull to refresh

Php + Cache + Tags = phpCacheTag

Reading time5 min
Views2.6K
Однажды, читая Хабр, я наткнулся на пост уважаемого dmitrykoterov про кеш и теги, который мне очень запал в душу. Как-раз тогда я возился с кешем и тоже, как и очень многие, пришёл к выводу, что ему(кешу) очень не хватает нативных тегов, по которым удобно было бы определённые порции кеша рубить одной строчкой. Рубить, не вдаваясь в именования переменных, которые под этими тегами числятся. Странно что осуществлением этой возможности занимаются сторонние разработчики, если говорить о самом популярном у нас memcache.

phpCacheTagВ итоге в свой очередной субботне-воскресный кодо-марафон я решил, всё же, заняться этим вопросом вплотную и написать универсальную библиотеку для любого кешевого бэкенда, будь то memcache или кеширование в файлах на чистом php. Библиотека написалалсь и поселилась на googlecode. Простите за английский, чуть ниже постараюсь исправиться.



Цель стояла написать изящно и так чтоб с лёгкостью можно было внедрить на любой сайт, как ужасно бы он ни был написан. Был выбран паттерн Singleton, для убодства внедрения, а так же исключительно статические вызовы публичных методов. На данный момент поддерживается только memcache, но нет ничего проще написать адаптеры для других систем кеширования.

Предположим, есть у нас вызов функции
$res = GetMeaningOfLife(true, false, 42);

в недрах которой проходят термоядерные реакции очень сложные вычисления. Соответственно, нужно резать кешировать.

Всё начинается с установки системы кэширования
CacheTag::SetBackend(CacheTag::BACKEND_MEMCACHE);

Теперь научим библиотеку добывать данные
CacheTag::SetFunction('GetMeaningOfLife', true, false, 42);

Кстати, если вместо 'GetMeaningOfLife' написать array($UniverseObject, 'GetMeaningOfLife') данные будут добываться из метода GetMeaningOfLife объекта UniverseObject.
Текущей единице кеша выдаётся своё имя. Оно формируется из имени вызываемой функции/метода, параметров функции и дополнительных кешевых параметров, о которых чуть ниже.

Затем заклеймим текущий элемент кеша тегами
CacheTag::SetTags(CacheTag::TAG_PURPOSE, CacheTag::TAG_LIFE);

Теги выглядят как CacheTag::TAG_YOURTAGNAME, очевидно что это всего лишь константы. Их нужно потрудиться обьявить самим в классе настроек. Здесь, кстати, предвижу вопрос «А почему настройки в классе? И вообще зачем отдельно, файлов-то раз два и обчёлся?». Класс легко наследовать. Теги осуществлены методом констант для удобного автодополнения, элементами массива было бы не то, да и вообще в этом классе все ваши личные настройки, не особо зависимые от основного функционала — для лёгкого и безопасного обновления.

Очень(очень) важно отметить, что, по сути, сейчас у нас в кеше переменная с именем purpose, или что там вы установили в значении TAG_PURPOSE. Для того чтоб полноценно воспользоваться системой, нам нужны дополнительные параметры, которые, собственно, уже и будут идентифицировать значения переменных в кеше. Теги просто логически заключают остальные, более мелкие значения в группы, удобные для удаления.

Подобно тегам, параметры тоже являются константами. И имеют вид CacheTag::PAR_YOURPARNAME. Служат они, для того что бы отличить одну единицу кеша от другой(эвркика!). Чтоб установить параметр, следует воспользоваться методом
СacheTag::SetParam(CacheTag::PARAM_PAGE, $currentPageNum);

Ещё раз отмечу: параметры — это не опциональная фича. Без них праздника не будет. И смысл тегов пропадает, так как имя переменной не задаётся явно, а собирается из указанных тегов и параметров.

Не редки моменты, когда громоздкая функция возвращает результат в зависимости от текущего пользователя, или, скажем, страницы в разбивке длинного контента(читай пагинации). Переменные эти могут подлым образом браться функцией неявно, магически «кастоваться» при помощи global и прочими ухищрениями. А параметры, как-раз очень удобно указывать перед запуском генерации кеша, удобно и аккуратно.

Теперь можно забирать данные
$res = CacheTag::Fetch();

На этом всё. Что же мы имеем в итоге:

Было
$res = GetMeaningOfLife(true, false, 42);

Стало
//$res = GetMeaningOfLife(true, false, 42);
CacheTag::SetFunction('GetMeaningOfLife', true, false, 42);
CacheTag::SetTags(CacheTag::TAG_PURPOSE, CacheTag::TAG_LIFE);
CacheTag::SetParam(CacheTag::PARAM_PAGE, $currentPageNum);
$res = CacheTag::Fetch();

Всё что нам понадобилось сделать — это закоментировать вызов исходной функции и добавить пару строчек. Никакой дополнительной логики и прочих if/else.
Отныне данные будут забираться сами, если их нет в кеше, и сохраняться на протяжении определённого в конфиге времени. Иначе результат будет возвращен из кеша.

Если вдруг нужно срочно отказаться от кеша, выключаем его в настройках. Все вызовы будут форсированно проходить через указанные функции. Судорожный find/replace по всему проекту не обязателен.

Рано или поздно наступает момент, когда кеш необходимо обновить. Ну, к примеру, если, вдруг, у вас
new BabyBorn('boy'); *

и смысл жизни в срочном порядке поменялся, вам прогодится метод
CacheTag::Flush();

Он употребляется вместо CacheTag::Fetch();. Т.е. задаём нужные теги, параметры и переменные, соответствующие этому набору будут помечены для удаления.
Всё, прошлый смысл жизни уже не актуален. При следующем обращении он будет взят заново.

Кстати, нередка практика начинающими, да и не очень, программистами выводить сгенерированный html-контент(print/echo/...) как и где придётся, вместо того чтоб возвращать его и выводить централизованно(я лично считаю что даже ob_* этого не оправдывает). Контент может выводится в нескольких местах, включая функции вызываемые из кешируемой. Такие проекты, порой, непросто кешировать стандартными способами, без изменения в их коде, что делать если не страшно, то просто не хочется. Но, хвала phpCacheTag, эта проблема отныне нас не беспокоит. В кеше под одним именем хранится как возвращаемое значение, так и контент выпущенный в стандартный поток во время выполнения функции/метода. Этот контент будет выведен(print) при запросе к этой переменной, как при реальной работе функции.

Подытожив всё вышесказанное, соберём всё воедино в самом простом примере:

error_reporting(E_ALL);
require_once(«CacheTag.class.php»);

function f()
{
sleep(1);
return 2;
}

CacheTag::SetBackend(CacheTag::BACKEND_MEMCACHE);
CacheTag::SetFunction('f');
CacheTag::SetTags(CacheTag::TAG_PRODUCT);
CacheTag::SetParam(CacheTag::PARAM_ID, 2);
CacheTag::SetTimeout(0.1);
CacheTag::Fetch();


Константы тегов, параметров и бэкендов создавайте в классе конфига. Их значения, кстати, неважны, главное чтоб они не пересекались.

По большому счёту это всё. Я постараюсь заставить себя быть хорошим программистом и написать ко всему этому делу полноценную документацию.

А пока что милости прошу пробовать, конструктивно критиковать и баг-репортить.

Счастливого кодинга.

Обновилось (24 Августа 2010):



версия: 0.1.5
  • Баг: Настройки не сбрасываются после вызова Flush()
  • Баг: Не выодится дебаговое сообщение для Replace()
  • Изменено: Изменён example.php
  • Изменено: Модифицирован Fetch(), что бы не обращаться к кэшу когда это не обязательно
  • Изменено: Возможно использовать нулевое значение тайм-лимита
  • Изменено: Memcache использует 127.0.0.1 вместо localhost




* Класс BabyBorn не входит в состав библиотеки. При необходимости, вы можете написать его самостоятельно.
Tags:
Hubs:
Total votes 19: ↑12 and ↓7+5
Comments42

Articles