Что такое PSR-6: Руководство по стандартам кэширования PHP

Источник: «What is PSR-6? A Beginner’s Guide to PHP Caching Standards»
PHP приложение тормозит из-за повторяющихся запросов к базе данных или неэффективного кэширования? Вы хотите, чтобы переключение между библиотеками кэширования было более простым? PSR-6 — стандарт PHP, обеспечивающий согласованный интерфейс для систем кэширования, делающий код гибким, эффективным и простым в сопровождении.

Какую проблему решает PSR-6

До появления PSR-6 каждая библиотека кэширования имела собственный уникальный принцип работы. Переход с Memcached на Redis, требовал значительных изменений в коде. Аналогично, переход на новый фреймворк часто означал освоение принципиально иного API кэширования.

PSR-6 исправляет ситуацию, предоставляя стандартизированный интерфейс кэширования, доступный всем библиотекам. Он делает кэширование предсказуемым и взаимозаменяемым - можно менять бэкенды кэширования, не переписывая приложение.

Концепции PSR-6

Pool

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

Item

Item представляет одну пару ключ/значение в Pool. Ключ является первичным уникальным идентификатором элемента и должен быть неизменяемым. Значение может быть изменено в любое время.

Ключевые интерфейсы в PSR-6

В основе PSR-6 лежат два главных интерфейса:

CacheItemPoolInterface

Основное назначение Cache\CacheItemPoolInterface — принимать ключ от вызывающей библиотеки и возвращать связанный с ним объект Cache\CacheItemInterface. Он также является основной точкой взаимодействия со всей коллекцией кэша. Вся настройка и инициализация пула возлагается на реализующую библиотеку.

Методы CacheItemPoolInterface:

CacheItemInterface

CacheItemInterface определяет элемент в системе кэширования. Каждый объект Item должен быть связан с определённым ключом, устанавливаемым в зависимости от системы реализации и обычно передаваемым объектом Cache\CacheItemPoolInterface.

Объект Cache\CacheItemInterface инкапсулирует хранение и извлечение элементов кэша. Каждый Cache\CacheItemInterface генерируется объектом Cache\CacheItemPoolInterface, ответственным за все необходимые настройки, а также за ассоциацию объекта с уникальным ключом. Объекты Cache\CacheItemInterface должны иметь возможность хранить и извлекать любой тип PHP-значений, определённых в разделе Data данного документа.

Вызывающие библиотеки не должны сами создавать объекты Item. Они могут быть запрошены только у объекта Pool через метод getItem(). Вызывающие библиотеки не должны предполагать, что элемент, созданный одной библиотекой реализации, совместим с пулом другой библиотеки реализации. Представляет отдельный элемент в кэше. Он предоставляет методы для управления данными элемента кэша и временем истечения срока действия.

Методы CacheItemInterface:

Пример реализации PSR-6

Рассмотрим простой пример реализации PSR-6-совместимой системы кэширования на основе файлов.

Создание класса элементов кэша

Сначала создадим класс для представления отдельных элементов кэша.

namespace MyApp\Cache;

use Psr\Cache\CacheItemInterface;
use DateTime;

class CacheItem implements CacheItemInterface
{
private string $key;
private $value;
private bool $isHit = false;
private ?DateTime $expiration = null;

public function __construct(string $key)
{
$this->key = $key;
}

public function getKey(): string
{
return $this->key;
}

public function get()
{
return $this->value;
}

public function isHit(): bool
{
return $this->isHit;
}

public function set($value): self
{
$this->value = $value;
return $this;
}

public function expiresAt(?DateTime $expiration): self
{
$this->expiration = $expiration;
return $this;
}

public function expiresAfter($time): self
{
$this->expiration = (new DateTime())->modify("+{$time} seconds");
return $this;
}

// Хелперы
public function setHit(bool $hit): void
{
$this->isHit = $hit;
}

public function getExpiration(): ?DateTime
{
return $this->expiration;
}
}

Создание пула кэша на основе файлов

Далее создадим класс управления элементами кэша и хранения их в файлах.

namespace MyApp\Cache;

use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\CacheItemInterface;

class FileCachePool implements CacheItemPoolInterface
{
private string $directory;

public function __construct(string $directory)
{
if (!is_dir($directory) && !mkdir($directory, 0777, true)) {
throw new \RuntimeException("Cannot create cache directory: {$directory}");
}
$this->directory = $directory;
}

public function getItem($key): CacheItemInterface
{
$filePath = $this->getFilePath($key);
$item = new CacheItem($key);

if (file_exists($filePath)) {
$data = unserialize(file_get_contents($filePath));
if (!$data['expiration'] || $data['expiration'] > time()) {
$item->set($data['value']);
$item->setHit(true);
}
}

return $item;
}

public function save(CacheItemInterface $item): bool
{
$filePath = $this->getFilePath($item->getKey());
$data = [
'value' => $item->get(),
'expiration' => $item->getExpiration()?->getTimestamp(),
];

return file_put_contents($filePath, serialize($data)) !== false;
}

public function clear(): bool
{
foreach (glob($this->directory . '/*.cache') as $file) {
unlink($file);
}
return true;
}

private function getFilePath(string $key): string
{
return $this->directory . '/' . sha1($key) . '.cache';
}
}

Использование кэша в приложении

Пример использования системы кэширования в приложении:

// Создание пула кэша с каталогом файлов
$cachePool = new FileCachePool(__DIR__ . '/cache');

// Получение элемента кэша
$item = $cachePool->getItem('user_123');

if (!$item->isHit()) {
// Элемент не найден в кэше, извлекаем из базы данных
$userData = getUserFromDatabase(123);
$item->set($userData)
->expiresAfter(3600); // Кэширование в течение 1 часа

// Сохранение элемента в кэше
$cachePool->save($item);
}

// Использование кэшированных данных
$user = $item->get();
print_r($user);

Зачем нужен PSR-6

Комментарии


Дополнительные материалы

Предыдущая Статья

CSS @import: Плюсы и минусы

Следующая Статья

Руководство по вебхукам в Laravel