For the complete documentation index, see llms.txt. This page is also available as Markdown.

Кэширование

Как кэшировать данные, помечать их тегами и сбрасывать группами

Кэш нужен там, где данные достаются дорого, а меняются редко: дерево разделов, счётчики, карта URL, список настроек. Вместо запроса к базе на каждый просмотр страницы результат считается один раз и потом отдаётся из хранилища.

В JohnCMS кэш — это сервис Johncms\Cache\CacheInterface. Внутри работает symfony/cache, но модуль об этом знать не должен: на каком хранилище стоит сайт, решает конфигурация, а код остаётся одинаковым.

Контракт основан на стандарте PSR-16 и добавляет к нему три вещи: remember(), rememberForever() и invalidateTags().

Быстрый старт

Внедрите CacheInterface через конструктор:

<?php

declare(strict_types=1);

namespace Johncms\Modules\MyModule\Application\Services;

use Johncms\Cache\CacheInterface;
use Johncms\Modules\MyModule\Domain\Repository\ArticleRepositoryInterface;

final readonly class PopularArticles
{
    /** Тег, под которым лежит всё кэшированное этим сервисом */
    private const CACHE_TAG = 'my-module';

    public function __construct(
        private CacheInterface $cache,
        private ArticleRepositoryInterface $repository,
    ) {
    }

    /**
     * @return array<int, string>
     */
    public function titles(): array
    {
        return $this->cache->remember(
            'my_module_popular',
            600,
            fn (): array => $this->repository->getPopularTitles(),
            [self::CACHE_TAG]
        );
    }
}

remember() — основной метод. Если значение в кэше есть, оно возвращается сразу; если нет — выполняется замыкание, результат сохраняется и возвращается. Обращения к базе не будет до тех пор, пока запись не истечёт или её не сбросят по тегу.

Не пишите «проверить, потом посчитать, потом положить» вручную через has() и set(). Между проверкой и чтением запись может исчезнуть, и код получит null там, где ждал массив. remember() закрывает этот случай сам.

Методы

Метод
Что делает

remember($key, $ttl, $callback, $tags)

Отдаёт значение из кэша, а на промахе считает его замыканием и сохраняет

rememberForever($key, $callback, $tags)

То же, но без срока: запись живёт, пока её не сбросят по тегу или не очистят кэш

invalidateTags(...$tags)

Помечает устаревшим всё, что лежит под этими тегами

get($key, $default)

Значение или $default, если записи нет

set($key, $value, $ttl)

Кладёт значение

delete($key)

Удаляет одну запись

has($key)

Есть ли запись

clear()

Очищает весь кэш приложения

getMultiple() / setMultiple() / deleteMultiple()

То же самое пачкой

Всё, кроме первых трёх строк, — стандарт PSR-16.

Срок жизни

Третий аргумент remember() — время жизни в секундах либо DateInterval. null означает «без срока» и равнозначен rememberForever().

Выбирайте срок по тому, насколько устаревшие данные допустимы на странице. Счётчик посетителей онлайн живёт десять секунд, счётчик сообщений форума — десять минут. Данные, которые меняются только по действию администратора, обычно кладут без срока и сбрасывают тегом.

Теги: сброс группами

Тег — это метка на записи. Одна запись может нести несколько тегов, а сброс тега делает недействительными все записи под ним, сколько бы их ни было.

Без тегов пришлось бы держать где-то список всех ключей и не забывать дополнять его при добавлении нового. С тегами достаточно пометить новую запись — метод сброса менять не нужно.

Правила:

  • Тег называется по тому, что за данные, а не откуда они читаются: news, counters, collections, my-module.

  • Держите тег константой рядом с ключами, которые он покрывает.

  • Сбрасывайте теги там, где данные меняются, — в use case сохранения и удаления, а не в контроллере.

Ключи и теги

Ключи и теги подчиняются ограничениям PSR-16: символы {}()/\@: запрещены и приводят к исключению Psr\SimpleCache\InvalidArgumentException. Буквы, цифры, точка, дефис и подчёркивание разрешены.

Начинайте ключ с имени модуля — news_subsections, collections_code_map, my_module_popular. Кэш общий на весь сайт, и одинаковые ключи из двух модулей затрут друг друга.

Что можно класть в кэш

Массивы, скаляры и простые объекты-DTO.

Замыкание должно возвращать значение всегда — в том числе когда данных нет. Пустой массив закэшируется и избавит от повторных запросов; null тоже допустим, но тогда remember() при каждом обращении будет считать заново.

Настройка

Кэш настраивается в config/autoload/cache.global.php, а на конкретном сервере переопределяется через cache.local.php (см. Конфигурационные файлы).

Драйверы

Драйвер
Когда подходит

filesystem

По умолчанию. Файлы в data/cache/app, работает на любом хостинге

apcu

Быстрее файлового, но память принадлежит одному веб-серверу. Нужно расширение APCu

redis

Установка на нескольких серверах. Нужно расширение redis либо пакет predis/predis

array

Только на время одного запроса. Предназначен для тестов

null

Ничего не хранит, каждое чтение — промах. Удобно при отладке

Теги работают на любом драйвере: даже те хранилища, которые сами тегов не умеют, оборачиваются так, чтобы invalidateTags() действовал.

Остальные параметры

  • namespace — префикс, отделяющий этот сайт от других в общем хранилище. По умолчанию берётся версия CMS, поэтому после обновления кэш начинается с чистого листа и старые записи не читаются.

  • default_lifetime — срок для записей, которые его не задали. 0 — без ограничения.

  • directory — где хранит файлы драйвер filesystem. nulldata/cache/app.

  • tags_storage — как файловый драйвер связывает теги с записями: auto (по умолчанию), symlink или files. В режиме auto система один раз проверяет, разрешает ли хостинг символические ссылки, и запоминает ответ. Менять это нужно только если проверка ошиблась — например, на хостинге, где симлинки создаются, но не работают.

Консольные команды

Команда
Что делает

cache:pool:clear

Очищает кэш приложения — то, что накэшировали модули

cache:pool:clear news counters

Сбрасывает только записи с указанными тегами

cache:pool:prune

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

cache:clear

Полностью очищает data/cache: кэш приложения, скомпилированный контейнер, маршруты, шаблоны

cache:pool:prune уже стоит в планировщике и выполняется ночью. Если планировщик на сайте не настроен, запускайте её вручную — иначе каталог data/cache будет расти.

Обе команды доступны и через задачи обслуживания в админке, без доступа к консоли.

После обновления CMS используйте cache:clear — он сбрасывает в том числе скомпилированный контейнер и шаблоны. Для повседневного «сбросить данные модулей» достаточно cache:pool:clear, который всё это не трогает.

Тесты

Для тестов есть настоящий кэш в памяти — Tests\Support\InMemoryCache:

Не подменяйте CacheInterface моком: мок метода remember() подтверждает лишь то, что метод вызвали, но не то, что запись получила тег и сбросится вместе с остальными. С настоящим кэшем проверяется поведение — сохранилось, сбросилось, посчиталось заново.

Чего делать не нужно

  • Перечислять ключи в методе очистки. Для этого есть теги: новый ключ достаточно пометить.

  • Кэшировать то, что зависит от текущего посетителя, — права, содержимое корзины, персональные списки, — без включения идентификатора в ключ. Кэш общий на весь сайт, и один посетитель увидит данные другого.

  • Заводить свой файловый кэш (file_put_contents в data/cache). Такой кэш не участвует ни в очистке, ни в сбросе по тегу, и его не видно ни одной из команд.

  • Оборачивать в кэш дешёвый запрос. Чтение записи по первичному ключу обходится дешевле, чем чтение файла кэша и распаковка значения.

Последнее обновление

Это было полезно?