Что такое 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)— ВозвращаетCacheItem, представляющий указанный ключ.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 для каждой библиотеки кэширования.
- Повышенная производительность: Сокращение обращений к базе данных и ускорение работы приложения.