<? phpukraine ДОКУМЕНТАЦІЯ
⌕Пошук по платформі
Документація українською

Переклад офіційної документації українською. Кожен розділ показує стан готовності: недоперекладене позначене відкрито, а не приховане.

ВЕРСІЯ
АКТУАЛЬНА Актуальний реліз. Переклад наздоганяє оригінал, розділи з низьким відсотком позначені у змісті.
ПОГЛИБЛЕНО · ПЕРЕКЛАДЕНО оновлено 5 вересня 2026

Кеш · Symfony

Кешування покращує продуктивність застосунку, зберігаючи результат дорогих операцій — запитів до бази даних, HTTP-запитів до зовнішніх сервісів чи важких обчислень, — щоб наступні запити могли прочитати його миттєво, замість обчислювати наново.

Компонент Symfony Cache реалізує і стандарт PSR-6, і простіші Cache Contracts для максимальної сумісності. Він створений заради продуктивності та стійкості й постачається з готовими до використання адаптерами для найпоширеніших бекендів кешування: Redis, Memcached, APCu, реляційні бази даних, файлова система тощо. Він також надає розширені можливості, як-от інвалідація на основі тегів і запобігання «навалі» на кеш (stampede prevention). Ви можете використовувати його в будь-якому PHP-застосунку, із Symfony або без неї.

Дивіться також

Ця стаття пояснює, як кешувати довільні дані в коді вашого застосунку. Якщо ви хочете кешувати цілі HTTP-відповіді й віддавати їх без запуску застосунку, прочитайте статтю HTTP Cache.

Встановлення

У Symfony-застосунках цей компонент уже встановлений, бо він є залежністю самого фреймворку. В інших застосунках виконайте цю команду, щоб встановити компонент перед використанням:

$ composer require symfony/cache

Якщо ви встановлюєте цей компонент поза застосунком Symfony, вам потрібно підключити файл vendor/autoload.php у вашому коді, щоб увімкнути механізм автозавантаження класів, наданий Composer. Прочитайте цю статтю для докладнішої інформації.

Пули кешу, адаптери та елементи

Перш ніж уперше скористатися кешем, ознайомтеся з його головними поняттями:

Елемент (Item) : Одна одиниця інформації, збережена як пара ключ/значення, де ключ — це унікальний ідентифікатор інформації, а значення — її вміст.

Пул (Pool) : Логічний репозиторій елементів кешу. Усі операції кешу (збереження елементів, пошук елементів тощо) виконуються через пул. Застосунки можуть визначати скільки завгодно пулів, і ключі кешу з різних пулів ніколи не конфліктують, навіть якщо вони мають спільний бекенд.

Адаптер (Adapter) : Клас, що реалізує власне механізм кешування для зберігання інформації у файловій системі, на сервері Redis, у базі даних тощо. Коли ви використовуєте компонент у будь-якому PHP-застосунку, ви створюєте пули шляхом інстанціювання адаптерів. У Symfony-застосунках адаптери — це шаблони, які використовуються для створення пулів через конфігурацію (наприклад, cache.adapter.redis).

Провайдер (Provider) : Сервіс, який деякі адаптери використовують для підключення до сховища (наприклад, зʼєднання з Redis або Memcached). Це поняття існує лише в Symfony-застосунках; коли як провайдер використовується рядок DSN, сервіс зʼєднання створюється автоматично.

Cache Contracts проти PSR-6

Цей компонент містить два різні підходи до кешування:

Cache Contracts: : Простий, але потужний спосіб кешувати значення на основі колбеків перерахунку.

Кешування PSR-6: : Універсальна система кешування, яка оперує пулами кешу та елементами кешу.

Рекомендується використовувати підхід Cache Contracts: він потребує менше шаблонного коду й типово надає захист від навали на кеш. Саме тому ця стаття пояснює всі можливості кешування через Cache Contracts. Якщо натомість вам потрібен універсальний API PSR-6 (наприклад, при інтеграції сторонньої бібліотеки, яка його вимагає), прочитайте розділ про використання пулів кешу PSR-6 у кінці цієї статті, який пояснює, як виконувати ті самі операції за допомогою того API.

Базове використання

Cache Contracts визначають лише два методи: get() і delete(). Методу set() немає, бо метод get() і отримує, і встановлює значення кешу.

Перше, що вам потрібно, — це пул кешу, тобто обʼєкт, що реалізує Symfony\Contracts\Cache\CacheInterface. У Symfony-застосунках вкажіть цей інтерфейс як тип аргументу сервісу чи контролера, щоб отримати впровадження пулу cache.app. Коли ви використовуєте компонент у будь-якому PHP-застосунку, створіть пул шляхом інстанціювання одного з адаптерів кешу:

// src/Service/NewsProvider.php
namespace App\Service;

use Symfony\Contracts\Cache\CacheInterface;

class NewsProvider
{
    // пул "cache.app" впроваджується завдяки autowiring
    public function __construct(
        private CacheInterface $cache,
    ) {
    }

    // ...
}
use Symfony\Component\Cache\Adapter\FilesystemAdapter;

$cache = new FilesystemAdapter();

Тепер ви можете отримувати й видаляти кешовані дані за допомогою цього обʼєкта. Перший аргумент методу get() — це ключ, довільний рядок, який ви асоціюєте з кешованим значенням, щоб пізніше його отримати. Другий аргумент — це PHP callable, який виконується, коли ключ не знайдено в кеші, щоб згенерувати й повернути значення:

use Symfony\Contracts\Cache\ItemInterface;

// callable буде виконано лише при промаху кешу
$value = $cache->get('my_cache_key', function (ItemInterface $item): string {
    $item->expiresAfter(3600);

    // ... виконайте якийсь HTTP-запит або важкі обчислення
    $computedValue = 'foobar';

    return $computedValue;
});

echo $value; // 'foobar'

// ... а щоб видалити ключ кешу
$cache->delete('my_cache_key');

Зверніть увагу

Використовуйте теги кешу, щоб видалити більше одного ключа за раз.

Колбек, переданий у метод get(), надає дві додаткові можливості:

  • Виявлення раннього закінчення терміну дії: викличте метод Symfony\Contracts\Cache\ItemInterface::isHit усередині колбека; якщо він повертає true, значення перераховується наперед, до настання дати закінчення терміну дії, через механізм запобігання навалі на кеш;
  • Відкидання значення: колбек може приймати другий аргумент bool &$save, переданий за посиланням. Якщо ви встановите $save у false всередині колбека, повернуте значення не буде збережено в бекенді.

Елементи кешу

Елементи кешу — це одиниці інформації, збережені в кеші як пара ключ/значення. У компоненті Cache вони представлені класом Symfony\Component\Cache\CacheItem. Вони використовуються і в Cache Contracts, і в інтерфейсах PSR-6.

Ключі та значення елементів кешу

Ключ елемента кешу — це звичайний рядок, який виступає його ідентифікатором, тож він має бути унікальним для кожного пулу кешу. Ви можете вільно обирати ключі, але вони мають містити лише літери (A-Z, a-z), цифри (0-9) та символи _ і .. Інші поширені символи (як-от { } ( ) / \ @ :) зарезервовані стандартом PSR-6 для майбутнього використання.

Значення елемента кешу може бути будь-якими даними, представленими типом, який PHP здатний серіалізувати, як-от базові типи (string, integer, float, boolean, null), масиви та обʼєкти.

Створення елементів кешу

Єдиний спосіб створити елементи кешу — через пули кешу. При використанні Cache Contracts вони передаються як аргументи в колбек перерахунку, де ви можете їх налаштувати. Наприклад, ви можете встановити їхній час закінчення терміну дії:

$productsCount = $cache->get('stats.products_count', function (ItemInterface $item): int {
    $item->expiresAfter(3600); // кешувати на 1 годину

    // ... обчислити значення
    return 4711;
});

Значення елемента кешу (тобто значення, повернуте колбеком) встановлюється автоматично, тож вам не доведеться мати справу з елементом кешу поза колбеком.

Термін дії елемента кешу

Типово елементи кешу зберігаються назавжди. На практиці це «постійне зберігання» може дуже різнитися залежно від адаптера, який ви використовуєте (наприклад, адаптер APCu втрачає свій вміст при перезапуску сервера).

Проте в деяких застосунках прийнято використовувати елементи кешу з коротшим часом життя. Розгляньмо, наприклад, застосунок, який кешує останні новини лише на одну хвилину. У таких випадках використовуйте метод expiresAfter(), щоб задати кількість секунд кешування елемента:

$latestNews->expiresAfter(60);  // 60 секунд = 1 хвилина

// цей метод також приймає екземпляри \DateInterval
$latestNews->expiresAfter(DateInterval::createFromDateString('1 hour'));

Елементи кешу визначають ще один повʼязаний метод — expiresAt(), — щоб задати точну дату й час, коли термін дії елемента закінчиться:

$mostPopularNews->expiresAt(new \DateTime('tomorrow'));

Пули кешу також можуть визначати типовий час життя, що застосовується до всіх елементів, які не визначають власного. Використовуйте опцію default_lifetime у Symfony-застосунках або аргумент конструктора адаптера, коли використовуєте компонент у будь-якому PHP-застосунку.

Доступні адаптери кешу

Адаптери кешу — це класи, що реалізують власне механізм кешування. Усі вони підтримують Cache Contracts та інтерфейси PSR-6, тож ви можете створювати пули кешу з будь-яким із них.

У Symfony-застосунках кілька адаптерів попередньо налаштовані як сервіси, тож ви можете використовувати їх при визначенні своїх пулів кешу:

Зверніть увагу

Є також спеціальний адаптер cache.adapter.system. Його рекомендується використовувати для системного кешу. Цей адаптер застосовує певну логіку, щоб динамічно обрати найкраще можливе сховище на основі вашої системи (або PHP-файли, або APCu).

Компонент надає й інші адаптери, які не є попередньо налаштованими сервісами в Symfony-застосунках (наприклад, ChainAdapter і PhpFilesAdapter). Прочитайте окрему статтю про кожен адаптер, щоб дізнатися, як їх налаштувати й використовувати:

  • cache/adapters/*

Налаштування кешу за допомогою FrameworkBundle

У Symfony-застосунках пули кешу визначаються конфігурацією framework.cache. Деякі з вбудованих адаптерів можна налаштувати через скорочення, які визначають типовий провайдер, використовуваний усіма пулами на основі цього адаптера:

# config/packages/cache.yaml
framework:
    cache:
        directory: '%kernel.cache_dir%/pools' # Only used with cache.adapter.filesystem

        default_doctrine_dbal_provider: 'doctrine.dbal.default_connection'
        default_psr6_provider: 'app.my_psr6_service'
        default_redis_provider: 'redis://localhost'
        default_valkey_provider: 'valkey://localhost'
        default_memcached_provider: 'memcached://localhost'
        default_pdo_provider: 'pgsql:host=localhost'
// config/packages/cache.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return App::config([
    'framework' => [
        'cache' => [
            'directory' => '%kernel.cache_dir%/pools', // Використовується лише з cache.adapter.filesystem
            'default_doctrine_dbal_provider' => 'doctrine.dbal.default_connection',
            'default_psr6_provider' => 'app.my_psr6_service',
            'default_redis_provider' => 'redis://localhost',
            'default_valkey_provider' => 'valkey://localhost',
            'default_memcached_provider' => 'memcached://localhost',
            'default_pdo_provider' => 'pgsql:host=localhost',
        ],
    ],
]);

Системний кеш і кеш застосунку

Два пули кешу завжди увімкнені типово: cache.system і cache.app.

cache.system використовується внутрішньо компонентами Symfony, як-от анотації, серіалізатор і валідація. Він також доступний для коду застосунку, але лише за певних обмежень:

  1. Записи мають виводитися з вихідного коду й бути відтворюваними під час прогріву кешу через CacheWarmer.
  2. Кешований вміст має змінюватися лише тоді, коли змінюється вихідний код (тобто при розгортанні, а не під час виконання); ставтеся до нього як до readonly після розгортання.

Типово cache.system використовує cache.adapter.system, який пише у файлову систему й ланцюжком підключає APCu, коли той доступний. У більшості випадків типовий варіант — правильний вибір.

Порада

Хоча кеш system можна переналаштувати, рекомендується зберігати типову конфігурацію, застосовану до нього Symfony.

cache.app — це універсальний кеш даних для коду застосунку й бандлів. Дані в цьому пулі не потрібно скидати при розгортанні. Типово він використовує cache.adapter.filesystem, але рекомендується налаштувати швидший адаптер, як-от Redis, коли він доступний (це гарантує, що кешовані дані переживуть розгортання й будуть спільними для кількох інстансів у багатосерверній конфігурації).

Власні пули типово використовують cache.app як свій адаптер, якщо не налаштовано інакше. При використанні autowiring cache.app автоматично впроваджується в будь-який аргумент сервісу, типізований як Psr\Cache\CacheItemPoolInterface, Symfony\Contracts\Cache\CacheInterface або Symfony\Contracts\Cache\NamespacedPoolInterface.

Ви можете налаштувати адаптер, який використовує кожен наперед визначений пул, через ключі app і system:

# config/packages/cache.yaml
framework:
    cache:
        app: cache.adapter.filesystem
        system: cache.adapter.system
// config/packages/cache.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return App::config([
    'framework' => [
        'cache' => [
            'app' => 'cache.adapter.filesystem',
            'system' => 'cache.adapter.system',
        ],
    ],
]);

Створення власних пулів (із просторами імен)

Ви також можете створювати більш налаштовані пули:

# config/packages/cache.yaml
framework:
    cache:
        default_memcached_provider: 'memcached://localhost'

        pools:
            # creates a "custom_thing.cache" service
            # autowireable via "CacheInterface $customThingCache"
            # uses the "app" cache configuration
            custom_thing.cache:
                adapter: cache.app

            # creates a "my_cache_pool" service
            # autowireable via "CacheInterface $myCachePool"
            my_cache_pool:
                adapter: cache.adapter.filesystem

            # uses the default_memcached_provider from above
            acme.cache:
                adapter: cache.adapter.memcached

            # control adapter's configuration
            foobar.cache:
                adapter: cache.adapter.memcached
                provider: 'memcached://user:password@example.com'

            # uses the "foobar.cache" pool as its backend but controls
            # the lifetime and (like all pools) has a separate cache namespace
            short_cache:
                adapter: foobar.cache
                default_lifetime: 60
// config/packages/cache.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return App::config([
    'framework' => [
        'cache' => [
            'default_memcached_provider' => 'memcached://localhost',
            'pools' => [
                // створює сервіс "custom_thing.cache"
                // autowireable через "CacheInterface $customThingCache"
                // використовує конфігурацію кешу "app"
                'custom_thing.cache' => [
                    'adapter' => 'cache.app',
                ],
                // створює сервіс "my_cache_pool"
                // autowireable через "CacheInterface $myCachePool"
                'my_cache_pool' => [
                    'adapter' => 'cache.adapter.filesystem',
                ],
                // використовує default_memcached_provider, наведений вище
                'acme.cache' => [
                    'adapter' => 'cache.adapter.memcached',
                ],
                // контроль над конфігурацією адаптера
                'foobar.cache' => [
                    'adapter' => 'cache.adapter.memcached',
                    'provider' => 'memcached://user:password@example.com',
                ],
                // використовує пул "foobar.cache" як свій бекенд, але контролює
                // час життя і (як усі пули) має окремий простір імен кешу
                'short_cache' => [
                    'adapter' => 'foobar.cache',
                    'default_lifetime' => 60,
                ],
            ],
        ],
    ],
]);

Кожен пул керує набором незалежних ключів кешу: ключі з різних пулів ніколи не конфліктують, навіть якщо вони мають спільний бекенд. Це досягається додаванням до ключів префікса — простору імен, згенерованого хешуванням назви пулу, назви класу адаптера кешу та налаштовуваного «зерна» (seed), яке типово дорівнює каталогу проєкту й класу скомпільованого контейнера.

Кожен власний пул стає сервісом, ID якого — це назва пулу (наприклад, custom_thing.cache). Для кожного пулу також створюється autowiring-аліас, що використовує camel case версію його назви: наприклад, custom_thing.cache можна впровадити автоматично, назвавши аргумент $customThingCache і типізувавши його або Symfony\Contracts\Cache\CacheInterface, або Psr\Cache\CacheItemPoolInterface:

use Symfony\Contracts\Cache\CacheInterface;
// ...

// з методу контролера
public function listProducts(CacheInterface $customThingCache): Response
{
    // ...
}

// у сервісі
public function __construct(private CacheInterface $customThingCache)
{
    // ...
}

Коли ви використовуєте компонент у будь-якому PHP-застосунку, простір імен пулів задається першим аргументом конструктора адаптерів, який також дозволяє налаштувати типовий час життя елементів:

use Symfony\Component\Cache\Adapter\FilesystemAdapter;

// створює пул, який додає до своїх ключів простір імен "my_app" і
// елементи якого типово закінчують термін дії через годину
$cache = new FilesystemAdapter('my_app', 3600);

Порада

Якщо вам потрібно, щоб простір імен був сумісним зі стороннім застосунком, ви можете взяти автоматичну генерацію під контроль, задавши атрибут namespace тегу сервісу cache.pool. Наприклад, ви можете перевизначити означення сервісу адаптера:

# config/services.yaml
services:
    # ...

    app.cache.adapter.redis:
        parent: 'cache.adapter.redis'
        tags:
            - { name: 'cache.pool', namespace: 'my_custom_namespace' }

Власні опції провайдера

Деякі провайдери мають специфічні опції, які можна налаштувати. RedisAdapter дозволяє створювати провайдери з опціями timeout, retry_interval тощо. Щоб використовувати ці опції з нетиповими значеннями, вам потрібно створити власний провайдер \Redis і використати його при налаштуванні пулу.

# config/packages/cache.yaml
framework:
    cache:
        pools:
            cache.my_redis:
                adapter: cache.adapter.redis
                provider: app.my_custom_redis_provider

services:
    app.my_custom_redis_provider:
        class: \Redis
        factory: ['Symfony\Component\Cache\Adapter\RedisAdapter', 'createConnection']
        arguments:
            - 'redis://localhost'
            - { retry_interval: 2, timeout: 10 }
// config/packages/cache.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Symfony\Component\Cache\Adapter\RedisAdapter;

return App::config([
    'framework' => [
        'cache' => [
            'pools' => [
                'cache.my_redis' => [
                    'adapter' => 'cache.adapter.redis',
                    'provider' => 'app.my_custom_redis_provider',
                ],
            ],
        ],
    ],
    'services' => [
        'app.my_custom_redis_provider' => [
            'class' => \Redis::class,
            'factory' => [RedisAdapter::class, 'createConnection'],
            'arguments' => ['redis://localhost', ['retry_interval' => 2, 'timeout' => 10]],
        ],
    ],
]);

Створення ланцюжка кешу

Різні адаптери кешу мають різні сильні й слабкі сторони. Одні можуть бути дуже швидкими, але оптимізованими для зберігання малих елементів, а інші здатні вмістити багато даних, але доволі повільні. Щоб отримати найкраще з обох світів, ви можете використати ланцюжок адаптерів.

Ланцюжок кешу обʼєднує кілька пулів кешу в один. Зберігаючи елемент у ланцюжку кешу, Symfony зберігає його в усіх пулах послідовно. Отримуючи елемент, Symfony намагається взяти його з першого пулу. Якщо його там не знайдено, вона пробує наступні пули, доки елемент не буде знайдено або не буде кинуто виняток. Через таку поведінку рекомендується визначати адаптери в ланцюжку в порядку від найшвидшого до найповільнішого.

Якщо при збереженні елемента в якомусь пулі стається помилка, Symfony зберігає його в інших пулах, і виняток не кидається. Пізніше, коли елемент буде отримано, Symfony автоматично збереже елемент в усіх пулах, де його бракує.

# config/packages/cache.yaml
framework:
    cache:
        pools:
            my_cache_pool:
                default_lifetime: 31536000  # One year
                adapters:
                  - cache.adapter.array
                  - cache.adapter.apcu
                  - {name: cache.adapter.redis, provider: 'redis://user:password@example.com'}
// config/packages/cache.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return App::config([
    'framework' => [
        'cache' => [
            'pools' => [
                'my_cache_pool' => [
                    'default_lifetime' => 31536000, // Один рік
                    'adapters' => [
                        'cache.adapter.array',
                        'cache.adapter.apcu',
                        ['name' => 'cache.adapter.redis', 'provider' => 'redis://user:password@example.com'],
                    ],
                ],
            ],
        ],
    ],
]);
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\ChainAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;

$cache = new ChainAdapter([
    new ArrayAdapter(),
    new ApcuAdapter(),
    new RedisAdapter(
        RedisAdapter::createConnection('redis://user:password@example.com')
    ),
], 31536000); // один рік

Створення підпросторів імен

Іноді вам потрібно створювати залежні від контексту варіації даних, які слід кешувати. Наприклад, дані для відображення сторінки дашборда можуть бути дорогими для генерації та унікальними для кожного користувача, тож ви не можете кешувати ті самі дані для всіх.

У таких випадках Symfony дозволяє створювати різні контексти кешу за допомогою просторів імен. Простір імен кешу — це довільний рядок, який ідентифікує набір повʼязаних елементів кешу. Більшість адаптерів кешу, наданих компонентом, реалізують Symfony\Contracts\Cache\NamespacedPoolInterface, який надає метод Symfony\Contracts\Cache\NamespacedPoolInterface::withSubNamespace (деякі адаптери, як-от PhpArrayAdapter і Psr16Adapter, не підтримують простори імен).

Цей метод дозволяє поміщати кешовані елементи в простір імен, прозоро додаючи префікс до їхніх ключів:

$userCache = $cache->withSubNamespace(sprintf('user-%d', $user->getId()));

$userCache->get('dashboard_data', function (ItemInterface $item): string {
    $item->expiresAfter(3600);

    return '...';
});

У цьому прикладі елемент кешу використовує ключ dashboard_data, але внутрішньо він буде збережений у просторі імен на основі ID поточного користувача. Це обробляється автоматично, тож вам не потрібно вручну додавати префікси до ключів, як-от user-27.dashboard_data.

У Symfony-застосунках пул cache.app і всі пули, визначені під опцією framework.cache.pools, підтримують підпростори імен. Ви також можете впровадити пул cache.app, типізувавши аргумент сервісу чи контролера як Symfony\Contracts\Cache\NamespacedPoolInterface.

Немає жодних настанов чи обмежень щодо того, як визначати простори імен кешу. Ви можете зробити їх настільки деталізованими або узагальненими, наскільки цього вимагає ваш застосунок:

$localeCache = $cache->withSubNamespace($request->getLocale());

$flagCache = $cache->withSubNamespace(
    $featureToggle->isEnabled('new_checkout') ? 'checkout-v2' : 'checkout-v1'
);

$channel = $request->attributes->get('_route')?->startsWith('api_') ? 'api' : 'web';
$channelCache = $cache->withSubNamespace($channel);

Порада

Ви можете поєднувати простори імен кешу з тегами кешу для більш складних потреб.

Немає вбудованого способу інвалідувати кеші за простором імен. Натомість рекомендований підхід — змінити сам простір імен. Саме тому прийнято включати статичні або динамічні дані версіонування до простору імен кешу:

// для простих застосунків може вистачити статичного номера версії, що інкрементується
$userCache = $cache->withSubNamespace(sprintf('v1-user-%d', $user->getId()));

// інші застосунки можуть використовувати динамічне версіонування на основі дати (наприклад, щомісячне)
$userCache = $cache->withSubNamespace(sprintf('%s-user-%d', date('Ym'), $user->getId()));

// або навіть інвалідувати кеш, коли дані користувача змінюються
$checksum = hash('xxh128', $user->getUpdatedAt()->format(DATE_ATOM));
$userCache = $cache->withSubNamespace(sprintf('user-%d-%s', $user->getId(), $checksum));

Запобігання навалі на кеш

Cache Contracts мають вбудоване запобігання навалі (Stampede prevention). Це усуває сплески навантаження на CPU в моменти, коли кеш холодний. Якщо приклад застосунку витрачає 5 секунд на обчислення даних, які кешуються на 1 годину, і ці дані запитуються 10 разів щосекунди, це означає, що переважно ви маєте влучання в кеш і все гаразд. Але через 1 годину застосунок отримує 10 нових запитів до холодного кешу. Тож дані обчислюються знову. Наступної секунди стається те саме. Тож дані обчислюються близько 50 разів, доки кеш не прогріється знову. Ось де вам потрібне запобігання навалі.

Перше рішення — використання блокування: дозволяти лише одному PHP-процесу (у межах хоста) обчислювати конкретний ключ за раз. Блокування вбудоване типово, тож вам не потрібно робити нічого, крім використання Cache Contracts.

Друге рішення також вбудоване при використанні Cache Contracts: замість чекати на повну затримку до закінчення терміну дії значення, перерахувати його наперед, до дати закінчення терміну дії. Алгоритм ймовірнісного раннього закінчення терміну дії (probabilistic early expiration) випадковим чином імітує промах кешу для одного користувача, тоді як інші й далі отримують кешоване значення. Ви можете керувати його поведінкою третім необовʼязковим параметром методу Symfony\Contracts\Cache\CacheInterface::get, який є числом із рухомою комою під назвою «beta».

Типово beta дорівнює 1.0, і вищі значення означають раніший перерахунок. Встановіть його в 0, щоб вимкнути ранній перерахунок, і в INF, щоб примусити негайний перерахунок:

use Symfony\Contracts\Cache\ItemInterface;

$beta = 1.0;
$value = $cache->get('my_cache_key', function (ItemInterface $item): string {
    $item->expiresAfter(3600);

    return '...';
}, $beta);

Зверніть увагу

Параметр beta має ефект лише тоді, коли кешований елемент визначає закінчення терміну дії. Крім того, запобігання навалі застосовується лише до Cache Contracts: при використанні методів PSR-6 (getItem(), save() тощо) ви маєте самі захищати застосунок від навали на кеш.

Налаштування власного marshaller для окремого пулу кешу

Нововведення у версії 8.1

Опцію marshaller для пулів кешу було введено в Symfony 8.1.

Наведена вище конфігурація декорує сервіс cache.default_marshaller, тож власний marshaller застосовується до всіх пулів кешу. Якщо вам потрібен власний marshaller лише для конкретних пулів, використовуйте натомість опцію marshaller:

# config/packages/cache.yaml
framework:
    cache:
        pools:
            cache.encrypted:
                adapter: cache.adapter.filesystem
                marshaller: 'app.sodium_marshaller'

services:
    app.sodium_marshaller:
        class: Symfony\Component\Cache\Marshaller\SodiumMarshaller
        arguments:
            - ['%env(base64:CACHE_DECRYPTION_KEY)%']
            - '@cache.default_marshaller'
// config/packages/cache.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Symfony\Component\Cache\Marshaller\SodiumMarshaller;

return App::config([
    'framework' => [
        'cache' => [
            'pools' => [
                'cache.encrypted' => [
                    'adapter' => 'cache.adapter.filesystem',
                    'marshaller' => 'app.sodium_marshaller',
                ],
            ],
        ],
    ],
    'services' => [
        'app.sodium_marshaller' => [
            'class' => SodiumMarshaller::class,
            'arguments' => [
                [env('CACHE_DECRYPTION_KEY')->base64()],
                service('cache.default_marshaller'),
            ],
        ],
    ],
]);

Цей підхід дозволяє поєднувати різні стратегії marshalling між пулами (наприклад, шифрувати дані в одному пулі й стискати їх в іншому), залишаючи іншим пулам типовий marshaller:

# config/packages/cache.yaml
framework:
    cache:
        pools:
            cache.tokens:
                adapter: cache.adapter.redis
                marshaller: 'app.sodium_marshaller'
            cache.large_data:
                adapter: cache.adapter.filesystem
                marshaller: 'app.deflate_marshaller'
            cache.regular:
                adapter: cache.adapter.filesystem

services:
    app.sodium_marshaller:
        class: Symfony\Component\Cache\Marshaller\SodiumMarshaller
        arguments:
            - ['%env(base64:CACHE_DECRYPTION_KEY)%']
            - '@cache.default_marshaller'

    app.deflate_marshaller:
        class: Symfony\Component\Cache\Marshaller\DeflateMarshaller
        arguments:
            - '@cache.default_marshaller'
// config/packages/cache.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Symfony\Component\Cache\Marshaller\DeflateMarshaller;
use Symfony\Component\Cache\Marshaller\SodiumMarshaller;

return App::config([
    'framework' => [
        'cache' => [
            'pools' => [
                'cache.tokens' => [
                    'adapter' => 'cache.adapter.redis',
                    'marshaller' => 'app.sodium_marshaller',
                ],
                'cache.large_data' => [
                    'adapter' => 'cache.adapter.filesystem',
                    'marshaller' => 'app.deflate_marshaller',
                ],
                'cache.regular' => [
                    'adapter' => 'cache.adapter.filesystem',
                ],
            ],
        ],
    ],
    'services' => [
        'app.sodium_marshaller' => [
            'class' => SodiumMarshaller::class,
            'arguments' => [
                [env('CACHE_DECRYPTION_KEY')->base64()],
                service('cache.default_marshaller'),
            ],
        ],
        'app.deflate_marshaller' => [
            'class' => DeflateMarshaller::class,
            'arguments' => [
                service('cache.default_marshaller'),
            ],
        ],
    ],
]);

Асинхронне обчислення значень кешу

Через алгоритм ймовірнісного раннього закінчення терміну дії деякі елементи кешу обираються для раннього закінчення, доки вони ще свіжі. Типово такі елементи кешу перераховуються синхронно. Проте в Symfony-застосунках ви можете обчислювати їх асинхронно, делегувавши обчислення значення фоновому обробнику за допомогою компонента Messenger. У цьому випадку, коли елемент запитується, його кешоване значення повертається негайно, а через шину Messenger надсилається Symfony\Component\Cache\Messenger\EarlyExpirationMessage.

Коли це повідомлення обробляється споживачем повідомлень, оновлене значення кешу обчислюється асинхронно. Наступного разу, коли елемент буде запитано, оновлене значення вже буде свіжим і повернеться.

Спершу створіть сервіс, який обчислюватиме значення елемента:

// src/Cache/CacheComputation.php
namespace App\Cache;

use Psr\Cache\CacheItemInterface;
use Symfony\Contracts\Cache\CallbackInterface;

class CacheComputation implements CallbackInterface
{
    public function __invoke(CacheItemInterface $item, bool &$save): string
    {
        $item->expiresAfter(5);

        // це випадковий приклад; тут ви маєте виконати власне обчислення
        return sprintf('#%06X', mt_rand(0, 0xFFFFFF));
    }
}

Це значення кешу буде запитано з контролера, іншого сервісу тощо. У наступному прикладі значення запитується з контролера:

// src/Controller/CacheController.php
namespace App\Controller;

use App\Cache\CacheComputation;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;

class CacheController extends AbstractController
{
    #[Route('/cache', name: 'cache')]
    public function index(CacheInterface $asyncCache, CacheComputation $cacheComputation): Response
    {
        // передайте в кеш метод сервісу, який оновлює елемент
        $cachedValue = $asyncCache->get('my_value', $cacheComputation);

        // ...
    }
}

Нарешті, налаштуйте новий пул кешу (наприклад, названий async.cache), який використовуватиме шину повідомлень для обчислення значень в обробнику:

# config/packages/framework.yaml
framework:
    cache:
        pools:
            async.cache:
                early_expiration_message_bus: messenger.default_bus

    messenger:
        transports:
            async_bus: '%env(MESSENGER_TRANSPORT_DSN)%'
        routing:
            'Symfony\Component\Cache\Messenger\EarlyExpirationMessage': async_bus
// config/packages/framework.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Symfony\Component\Cache\Messenger\EarlyExpirationMessage;

return App::config([
    'framework' => [
        'cache' => [
            'pools' => [
                'async.cache' => [
                    'early_expiration_message_bus' => 'messenger.default_bus',
                ],
            ],
        ],
        'messenger' => [
            'transports' => [
                'async_bus' => env('MESSENGER_TRANSPORT_DSN'),
            ],
            'routing' => [
                EarlyExpirationMessage::class => 'async_bus',
            ],
        ],
    ],
]);

Тепер ви можете запустити споживача:

$ php bin/console messenger:consume async_bus

Ось і все! Тепер, щоразу коли елемент запитується з цього пулу кешу, його кешоване значення повертатиметься негайно. Якщо він буде обраний для раннього закінчення терміну дії, через шину буде надіслано повідомлення, щоб запланувати фонове обчислення для оновлення значення.

Інвалідація кешу

Інвалідація кешу — це процес видалення всіх кешованих елементів, повʼязаних зі зміною стану вашої моделі. Найпростіший вид інвалідації — це пряме видалення елемента. Але коли стан первинного ресурсу поширився на кілька кешованих елементів, підтримувати їх синхронізованими може бути складно.

Компонент Symfony Cache надає два механізми, що допомагають розвʼязати цю проблему:

  • Інвалідація на основі тегів — для керування залежностями даних;
  • Інвалідація на основі закінчення терміну дії — для залежностей, повʼязаних із часом.

Використання тегів кешу

У застосунках із багатьма ключами кешу може бути корисно організувати збережені дані так, щоб інвалідувати кеш ефективніше. Для цього прикріпіть один або кілька тегів до кожного кешованого елемента. Кожен тег — це звичайний рядковий ідентифікатор, який ви можете будь-коли використати, щоб видалити всі елементи, повʼязані з цим тегом.

Щоб прикріпити теги до кешованих елементів, використовуйте метод Symfony\Contracts\Cache\ItemInterface::tag. Щоб видалити всі елементи, повʼязані з певним тегом, використовуйте метод Symfony\Contracts\Cache\TagAwareCacheInterface::invalidateTags пулу кешу:

use Symfony\Contracts\Cache\ItemInterface;
use Symfony\Contracts\Cache\TagAwareCacheInterface;

class SomeClass
{
    // використання autowiring для впровадження пулу "my_cache_pool",
    // визначеного в конфігурації, показаній нижче
    public function __construct(
        private TagAwareCacheInterface $myCachePool,
    ) {
    }

    public function someMethod(): void
    {
        $value0 = $this->myCachePool->get('item_0', function (ItemInterface $item): string {
            $item->tag(['foo', 'bar']);

            return 'debug';
        });

        $value1 = $this->myCachePool->get('item_1', function (ItemInterface $item): string {
            $item->tag('foo');

            return 'debug';
        });

        // видалити всі ключі кешу, позначені тегом "bar"
        $this->myCachePool->invalidateTags(['bar']);

        // якщо ви знаєте ключ кешу, ви також можете видалити елемент напряму
        $this->myCachePool->delete('item_1');
    }
}
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
use Symfony\Contracts\Cache\ItemInterface;

$cache = new TagAwareAdapter(new FilesystemAdapter());

$value0 = $cache->get('item_0', function (ItemInterface $item): string {
    $item->tag(['foo', 'bar']);

    return 'debug';
});

$value1 = $cache->get('item_1', function (ItemInterface $item): string {
    $item->tag('foo');

    return 'debug';
});

// видалити всі ключі кешу, позначені тегом "bar"
$cache->invalidateTags(['bar']);

// якщо ви знаєте ключ кешу, ви також можете видалити елемент напряму
$cache->delete('item_1');

Інвалідація за тегами дуже зручна, коли відстежувати ключі кешу стає складно.

Щоб увімкнути цю можливість, пул кешу має реалізовувати Symfony\Contracts\Cache\TagAwareCacheInterface. У Symfony-застосунках використовуйте опцію tags пулу:

# config/packages/cache.yaml
framework:
    cache:
        pools:
            my_cache_pool:
                adapter: cache.adapter.redis_tag_aware
                tags: true
// config/packages/cache.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return App::config([
    'framework' => [
        'cache' => [
            'pools' => [
                'my_cache_pool' => [
                    'adapter' => 'cache.adapter.redis_tag_aware',
                    'tags' => true,
                ],
            ],
        ],
    ],
]);

Зверніть увагу

У Symfony-застосунках, коли аргумент сервісу чи контролера типізовано як Symfony\Contracts\Cache\TagAwareCacheInterface, autowiring впроваджує сервіс cache.app.taggable — пул із підтримкою тегів на основі пулу cache.app. Це означає, що ви можете використовувати теги кешу, не визначаючи жодного власного пулу.

Теги типово зберігаються в тому самому пулі. У більшості сценаріїв це добре. Але іноді може бути краще зберігати теги в іншому пулі. Цього можна досягти, вказавши адаптер.

# config/packages/cache.yaml
framework:
    cache:
        pools:
            my_cache_pool:
                adapter: cache.adapter.redis
                tags: tag_pool
            tag_pool:
                adapter: cache.adapter.apcu
// config/packages/cache.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return App::config([
            'framework' => [
                'cache' => [
                    'pools' => [
                        'my_cache_pool' => [
                            'adapter' => 'cache.adapter.redis',
                            'tags' => 'tag_pool',
                        ],
                        'tag_pool' => [
                            'adapter' => 'cache.adapter.apcu',
                        ],
                    ],
                ],
            ],
        ]);

Адаптери з підтримкою тегів

Щоб зберігати теги, треба обгорнути адаптер кешу класом Symfony\Component\Cache\Adapter\TagAwareAdapter або реалізувати Symfony\Contracts\Cache\TagAwareCacheInterface разом із його методом invalidateTags(). У застосунках Symfony опція tags: true, показана в попередньому розділі, обгортає адаптер пулу автоматично.

Примітка

Для бекенда Redis розгляньте RedisTagAwareAdapter, оптимізований саме для цього. Для файлової системи - FilesystemTagAwareAdapter.

Клас Symfony\Component\Cache\Adapter\TagAwareAdapter реалізує миттєву інвалідацію (складність за часом - O(N), де N - кількість інвалідованих тегів). Йому потрібен один або два адаптери кешу: перший, обовʼязковий, зберігає елементи кешу; другий, необовʼязковий, зберігає теги та номер версії їх інвалідації (концептуально це схоже на дату останньої інвалідації). Коли використовується лише один адаптер, елементи й теги лежать в одному місці. Із двома адаптерами можна, наприклад, тримати великі елементи кешу на файловій системі чи в базі даних, а теги - у базі Redis, щоб синхронізувати всі фронти й мати дуже швидкі перевірки інвалідації:

use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\Adapter\TagAwareAdapter;

$cache = new TagAwareAdapter(
    // Адаптер для елементів кешу
    new FilesystemAdapter(),
    // Адаптер для тегів
    new RedisAdapter('redis://localhost')
);

У застосунках Symfony саме це й робить за вас опція tags: tag_pool з попереднього розділу.

Примітка

Symfony\Component\Cache\Adapter\TagAwareAdapter реалізує Symfony\Component\Cache\PruneableInterface, тож ручне очищення протермінованих записів кешу доступне через виклик методу prune() (за умови, що обгорнутий адаптер сам реалізує Symfony\Component\Cache\PruneableInterface).

Очищення елементів кешу (pruning)

Частина бекендів кешу, як-от Redis, Memcached і APCu, прибирає протерміновані елементи самостійно. Інші, зокрема кеші на файловій системі чи базі даних, такого механізму не мають. Наприклад, FilesystemAdapter не видаляє протермінований елемент, доки цей елемент не запитають явно і не зʼясують, що він протермінований. За певних навантажень застарілі записи можуть жити ще довго після закінчення терміну придатності, і зайві протерміновані елементи зʼїдають помітний обсяг диска або памʼяті.

Щоб цьому зарадити, компонент надає Symfony\Component\Cache\PruneableInterface з методом Symfony\Component\Cache\PruneableInterface::prune, який видаляє зі сховища всі протерміновані елементи. Адаптери, яким ця можливість потрібна, реалізують його: ChainAdapter, DoctrineDbalAdapter, FilesystemAdapter, PdoAdapter і PhpFilesAdapter, а також адаптери, що обгортають інші адаптери (TagAwareAdapter, ProxyAdapter, Psr16Adapter) - вони делегують очищення обгорнутому адаптеру:

use Symfony\Component\Cache\Adapter\FilesystemAdapter;

$cache = new FilesystemAdapter('app.cache');
// ... виконуємо кілька операцій set і get
$cache->prune();

Реалізація ChainAdapter власної логіки очищення не містить. Натомість виклик методу Symfony\Component\Cache\Adapter\ChainAdapter::prune делегується всім сумісним адаптерам кешу в ланцюжку (а ті, що не реалізують PruneableInterface, тихо пропускаються):

use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Adapter\ChainAdapter;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Adapter\PdoAdapter;
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;

$cache = new ChainAdapter([
    new ApcuAdapter(),       // НЕ реалізує PruneableInterface
    new FilesystemAdapter(), // реалізує PruneableInterface
    new PdoAdapter(),        // реалізує PruneableInterface
    new PhpFilesAdapter(),   // реалізує PruneableInterface
    // ...
]);

// prune проксіює виклик до PdoAdapter, FilesystemAdapter і PhpFilesAdapter,
// тихо пропускаючи ApcuAdapter
$cache->prune();

Порада

У застосунках Symfony протерміновані елементи всіх пулів очищаються такою командою:

$ php bin/console cache:pool:prune

Очищення кешу

У застосунках Symfony елементи з пулів кешу видаляються командами cache:pool:*. Переглянути всі доступні пули кешу:

$ php bin/console cache:pool:list

Видалити один елемент з одного пулу:

$ php bin/console cache:pool:delete <cache-pool-name> <cache-key-name>

# видаляє елемент "cache_key" з пулу "cache.app"
$ php bin/console cache:pool:delete cache.app cache_key

Очистити один пул:

$ php bin/console cache:pool:clear my_cache_pool

Очистити всі пули кешу:

$ php bin/console cache:pool:clear --all

Очистити всі пули кешу, крім деяких:

$ php bin/console cache:pool:clear --all --exclude=my_cache_pool --exclude=another_cache_pool

Очистити кеш за тегом (тегами):

# інвалідувати tag1 у всіх пулах з підтримкою тегів
$ php bin/console cache:pool:invalidate-tags tag1

# інвалідувати tag1 і tag2 у всіх пулах з підтримкою тегів
$ php bin/console cache:pool:invalidate-tags tag1 tag2

# інвалідувати tag1 і tag2 у пулі cache.app
$ php bin/console cache:pool:invalidate-tags tag1 tag2 --pool=cache.app

# інвалідувати tag1 і tag2 у пулах cache1 і cache2
$ php bin/console cache:pool:invalidate-tags tag1 tag2 -p cache1 -p cache2

Пули можна також групувати в «cache clearers», щоб очищати кілька повʼязаних пулів за раз. За замовчуванням є 3 cache clearer-и:

  • cache.global_clearer: очищає всі елементи кешу в кожному пулі;
  • cache.system_clearer: очищає пули, які використовує команда bin/console cache:clear;
  • cache.app_clearer: типовий clearer, що очищає cache.app і власні пули.

Передайте назву clearer-а команді cache:pool:clear, щоб очистити всі його пули. Наприклад, щоб очистити всі кеші всюди:

$ php bin/console cache:pool:clear cache.global_clearer

Marshalling (серіалізація) даних

Примітка

Marshalling і серіалізація - схожі поняття. Серіалізація перетворює стан обʼєкта на формат, придатний для зберігання (наприклад, у файлі). Marshalling перетворює на такий формат і стан обʼєкта, і його кодову базу, щоб дані можна було зберегти чи передати.

Unmarshalling обʼєкта дає копію оригіналу, за потреби з автоматичним завантаженням визначень класів цього обʼєкта.

Symfony використовує marshaller-и (класи, що реалізують Symfony\Component\Cache\Marshaller\MarshallerInterface) для обробки елементів кешу перед збереженням. Вони спроєктовані так, щоб обгортати один одного: кожен застосовує власну обробку до результату попереднього.

Symfony\Component\Cache\Marshaller\DefaultMarshaller типово використовує PHP-функцію serialize(), але за бажанням можна взяти igbinary_serialize() з розширення Igbinary:

use Symfony\Component\Cache\Adapter\RedisAdapter;
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
use Symfony\Component\Cache\Marshaller\DeflateMarshaller;

$marshaller = new DeflateMarshaller(new DefaultMarshaller());
// за бажанням можна використати розширення Igbinary, якщо воно встановлене
// $marshaller = new DeflateMarshaller(new DefaultMarshaller(useIgbinarySerialize: true));

$cache = new RedisAdapter(new \Redis(), 'namespace', 0, $marshaller);

Є й інші marshaller-и, які перед збереженням шифрують або стискають дані.

У застосунках Symfony marshaller, спільний для всіх пулів кешу, визначає сервіс cache.default_marshaller. Декоруйте цей сервіс, щоб змінити обробку кешованих даних, як показано в наступному розділі.

Шифрування кешу

Щоб зашифрувати кеш засобами libsodium, скористайтеся Symfony\Component\Cache\Marshaller\SodiumMarshaller.

Спершу згенеруйте безпечний ключ і додайте його до свого сховища секретів під назвою CACHE_DECRYPTION_KEY:

$ php -r 'echo base64_encode(sodium_crypto_box_keypair());'

Далі зареєструйте сервіс SodiumMarshaller із цим ключем:

# config/packages/cache.yaml

# ...
services:
    Symfony\Component\Cache\Marshaller\SodiumMarshaller:
        decorates: cache.default_marshaller
        arguments:
            - ['%env(base64:CACHE_DECRYPTION_KEY)%']
            # використовуйте кілька ключів, щоб їх ротувати
            #- ['%env(base64:CACHE_DECRYPTION_KEY)%', '%env(base64:OLD_CACHE_DECRYPTION_KEY)%']
            - '@.inner'
// config/packages/cache.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Symfony\Component\Cache\Marshaller\SodiumMarshaller;

return App::config([
    'services' => [
        SodiumMarshaller::class => [
            'decorates' => 'cache.default_marshaller',
            'arguments' => [
                [env('CACHE_DECRYPTION_KEY')->base64()],
                // використовуйте кілька ключів, щоб їх ротувати
                // [env('CACHE_DECRYPTION_KEY')->base64(), env('OLD_CACHE_DECRYPTION_KEY')->base64()]
                service('.inner'),
            ],
        ],
    ],
]);

Небезпека

Так шифруються значення елементів кешу, але не ключі. Пильнуйте, щоб чутливі дані не потрапляли в ключі.

Коли налаштовано кілька ключів, перший використовується для читання й запису, а решта - лише для читання. Щойно всі елементи кешу, зашифровані старим ключем, протермінуються, OLD_CACHE_DECRYPTION_KEY можна прибрати повністю.

Використання PSR-6 пулів кешу

Попередні розділи цієї статті пояснювали, як кешувати вміст за допомогою рекомендованих Cache Contracts. Цей розділ показує ті самі операції через загальний API кешування PSR-6 - він може знадобитися, наприклад, при інтеграції сторонньої бібліотеки, яка його вимагає.

PSR-6 пули кешу реалізують Psr\Cache\CacheItemPoolInterface: у застосунках Symfony вкажіть цей інтерфейс як тип аргументу сервісу чи контролера, щоб отримати пул cache.app; у будь-якому PHP-застосунку створіть екземпляр одного з адаптерів кешу (повні приклади - у розділі про базове використання). Описані раніше ключі та значення елементів кешу й методи задання терміну придатності стосуються й PSR-6.

Пошук елементів кешу

Пули кешу визначають три методи для пошуку елементів. Найпоширеніший - getItem($key), який повертає елемент кешу за вказаним ключем:

$latestNews = $cache->getItem('latest_news');

Якщо для цього ключа елемент не визначено, метод поверне не null, а порожній обʼєкт, що реалізує клас Symfony\Component\Cache\CacheItem.

Коли потрібно дістати кілька елементів кешу одночасно, використовуйте метод getItems([$key1, $key2, ...]):

$stocks = $cache->getItems(['AAPL', 'FB', 'GOOGL', 'MSFT']);

Знову ж таки, якщо котрийсь із ключів не відповідає дійсному елементу кешу, ви отримаєте не null, а порожній обʼєкт CacheItem.

Останній метод, повʼязаний з отриманням елементів, - hasItem($key); він повертає true, якщо елемент із таким ключем існує:

$hasBadges = $cache->hasItem('user_'.$userId.'_badges');

Влучання й промахи кешу

Кешування важливе для продуктивності застосунку, але застосунок не повинен від нього залежати. Стандарт PSR-6 прямо зазначає: помилки кешування не мають призводити до збоїв застосунку.

Тому метод getItem() завжди повертає обʼєкт, що реалізує інтерфейс Psr\Cache\CacheItemInterface, навіть коли елемента кешу не існує. Отже, вам не доведеться мати справу з поверненням null, і в кеші можна безпечно зберігати такі значення, як false і null.

Щоб зрозуміти, чи повернений обʼєкт містить значення зі сховища, кеші оперують поняттями влучань (hits) і промахів (misses):

  • Влучання (cache hits) відбуваються, коли запитаний елемент знайдено в кеші, його значення не пошкоджене й не невалідне, а термін придатності не минув;
  • Промахи (cache misses) - протилежність влучань: елемента в кеші немає, його значення з якоїсь причини пошкоджене чи невалідне, або елемент протермінувався.

Обʼєкти елементів кешу мають булевий метод isHit(), який повертає true для влучань:

$latestNews = $cache->getItem('latest_news');

if (!$latestNews->isHit()) {
    // виконуємо якісь важкі обчислення
    $news = ...;
    $cache->save($latestNews->set($news));
} else {
    $news = $latestNews->get();
}

Збереження елементів кешу

На відміну від Cache Contracts, API PSR-6 не задає значення елементів кешу автоматично. Для цього використовуйте метод Psr\Cache\CacheItemInterface::set:

// зберігаємо просте ціле число
$productsCount = $cache->getItem('stats.products_count');
$productsCount->set(4711);

// зберігаємо масив
$productsCount->set([
    'category1' => 4711,
    'category2' => 2387,
]);

Ключ і значення будь-якого елемента кешу можна дістати відповідними getter-методами:

$cacheItem = $cache->getItem('exchange_rate');
// ...
$key = $cacheItem->getKey();
$value = $cacheItem->get();

Задане значення елемента ще не зберігається в кеші. Найчастіше для цього викликають Psr\Cache\CacheItemPoolInterface::save, який зберігає елемент негайно (повертає true, якщо елемент збережено, або false, якщо сталася помилка):

$userFriends = $cache->getItem('user_'.$userId.'_friends');
$userFriends->set($user->getFriends());
$isSaved = $cache->save($userFriends);

Іноді краще не зберігати обʼєкти одразу, щоб підвищити продуктивність застосунку. У таких випадках позначайте елементи кешу як «готові до збереження» методом Psr\Cache\CacheItemPoolInterface::saveDeferred, а коли настане час зберегти їх усі, викликайте Psr\Cache\CacheItemPoolInterface::commit:

$isQueued = $cache->saveDeferred($userFriends);
// ...
$isQueued = $cache->saveDeferred($userPreferences);
// ...
$isQueued = $cache->saveDeferred($userRecentProducts);
// ...
$isSaved = $cache->commit();

Метод saveDeferred() повертає true, коли елемент кешу успішно додано до «черги збереження», інакше false. Метод commit() повертає true, коли всі відкладені елементи успішно збережено, інакше false.

Видалення елементів кешу

Пули кешу мають методи для видалення одного елемента, кількох або всіх. Найпоширеніший - Psr\Cache\CacheItemPoolInterface::deleteItem, який видаляє елемент за вказаним ключем (повертає true, коли елемент успішно видалено або його не існує, інакше false):

$isDeleted = $cache->deleteItem('user_'.$userId);

Метод Psr\Cache\CacheItemPoolInterface::deleteItems видаляє кілька елементів кешу одночасно (повертає true, лише якщо видалено всі елементи, навіть коли якісь із них не існують):

$areDeleted = $cache->deleteItems(['category1', 'category2']);

Нарешті, щоб прибрати всі елементи кешу зі сховища пулу, використовуйте метод Psr\Cache\CacheItemPoolInterface::clear (повертає true, коли всі елементи успішно видалено):

$cacheIsEmpty = $cache->clear();

Порада

У застосунках Symfony елементи з пулів кешу можна видаляти й консольними командами.

Сумісність між кешами PSR-6 і PSR-16

Буває, що у вас є обʼєкт кешу, який реалізує стандарт PSR-16, а передати його треба обʼєкту, який очікує пул кешу PSR-6. Або навпаки. Компонент Cache містить два класи для двосторонньої сумісності між кешами PSR-6 і PSR-16.

Використання обʼєкта кешу PSR-16 як кешу PSR-6

Розглянемо клас, якому потрібен обʼєкт пулу кешу PSR-6:

use Psr\Cache\CacheItemPoolInterface;

// приклад класу, якому потрібен обʼєкт кешу PSR-6
class GitHubApiClient
{
    // ...

    public function __construct(CacheItemPoolInterface $cachePool)
    {
        // ...
    }
}

Якщо у вас уже є обʼєкт кешу PSR-16 і ви хочете передати його цьому класу, обгорніть його класом Symfony\Component\Cache\Adapter\Psr16Adapter, який робить його сумісним з PSR-6:

use Symfony\Component\Cache\Adapter\Psr16Adapter;

// $psr16Cache - це обʼєкт PSR-16, який ви хочете використати як PSR-6

// кеш PSR-6, що всередині використовує ваш кеш!
$psr6Cache = new Psr16Adapter($psr16Cache);

// тепер використовуйте його де завгодно
$githubApiClient = new GitHubApiClient($psr6Cache);

Використання обʼєкта кешу PSR-6 як кешу PSR-16

Тепер розглянемо клас, якому потрібен обʼєкт кешу PSR-16:

use Psr\SimpleCache\CacheInterface;

// приклад класу, якому потрібен обʼєкт кешу PSR-16
class GitHubApiClient
{
    // ...

    public function __construct(CacheInterface $cache)
    {
        // ...
    }
}

Якщо у вас уже є обʼєкт пулу кешу PSR-6 і ви хочете передати його цьому класу, обгорніть його класом Symfony\Component\Cache\Psr16Cache, який робить його сумісним з PSR-16:

use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\Cache\Psr16Cache;

// обʼєкт кешу PSR-6, який ви хочете використати
$psr6Cache = new FilesystemAdapter();

// кеш PSR-16, що всередині використовує ваш кеш!
$psr16Cache = new Psr16Cache($psr6Cache);

// тепер використовуйте його де завгодно
$githubApiClient = new GitHubApiClient($psr16Cache);
Стан перекладу

Перекладаємо з офіційної документації, розділ за розділом, і не ховаємо недоперекладене. Помітили неточність у терміні чи реченні: напишіть, виправимо.

46%
Готовності
2
У роботі
14
Ще не перекладено
Глосарій термінів