Что такое 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
:
getItem($key)
— ВозвращаетCache
Item
, представляющий указанный ключ.getItems(array $keys = array())
— Возвращает набор элементов кэша.hasItem($key)
— Подтверждает, содержит ли кэш указанный элемент кэша.clear()
— Удаляет все элементы пула.deleteItem($key)
— Удаляет элемент из пула.deleteItems(array $keys)
— Удаляет несколько элементов из пула.save(CacheItemInterface $item)
— Немедленно сохраняет элемент кэша.saveDeferred(CacheItemInterface $item)
— Устанавливает элемент кэша, для последующего сохранения.commit()
— Сохраняет все отложенные элементы кэша.
CacheItemInterface
CacheItemInterface
определяет элемент в системе кэширования. Каждый объект Item
должен быть связан с определённым ключом, устанавливаемым в зависимости от системы реализации и обычно передаваемым объектом Cache\CacheItemPoolInterface
.
Объект Cache\CacheItemInterface
инкапсулирует хранение и извлечение элементов кэша. Каждый Cache\CacheItemInterface
генерируется объектом Cache\CacheItemPoolInterface
, ответственным за все необходимые настройки, а также за ассоциацию объекта с уникальным ключом. Объекты Cache\CacheItemInterface
должны иметь возможность хранить и извлекать любой тип PHP-значений, определённых в разделе Data данного документа.
Вызывающие библиотеки не должны сами создавать объекты Item
. Они могут быть запрошены только у объекта Pool
через метод getItem()
. Вызывающие библиотеки не должны предполагать, что элемент, созданный одной библиотекой реализации, совместим с пулом другой библиотеки реализации. Представляет отдельный элемент в кэше. Он предоставляет методы для управления данными элемента кэша и временем истечения срока действия.
Методы CacheItemInterface
:
getKey()
— Возвращает ключ текущего элемента кэша.get()
— Извлекает значение элемента из кэша, связанного с ключом этого объекта.isHit()
— Подтверждает, что поиск элемента кэша привёл к попаданию в кэш.set($value)
— Устанавливает значение, представленное этим элементом кэша.expiresAt($expiration)
— Устанавливает время истечения (\DateTimeInterface|null
) срока действия для данного элемента кэша.expiresAfter($time)
— Устанавливает время истечения (int|\DateInterval|null
) срока действия для данного элемента кэша.
Пример реализации 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
- Гибкость: Легко менять бэкенды кэширования (например, с Redis на Memcached), не переписывая код.
- Согласованность: Стандартизированные методы избавляют от необходимости изучать новый API для каждой библиотеки кэширования.
- Повышенная производительность: Сокращение обращений к базе данных и ускорение работы приложения.