Hello everyone!
Is your application running slow due to repeated database queries? Or have trouble switching between different caching libraries? Let’s dive into PSR-6, the standard that makes caching in PHP predictable and interchangeable!
This article is part of the PHP PSR standards series. If you're new, it's a good idea to start with PSR-1 basics.
Prior to PSR-6, each cache library had its own unique way of working. Want to switch from Memcached to Redis? Rewrite your code. Migrating from one framework to another? Learn the new caching API. PSR-6 solves this problem by providing a common interface that all cache libraries can implement.
Let’s take a look at the two main players:
CacheItemPoolInterface
This is your cache manager. Think of it as a warehouse where you can store and retrieve items:
<code class="language-php"><?php namespace Psr\Cache; interface CacheItemPoolInterface { public function getItem($key); public function getItems(array $keys = array()); public function hasItem($key); public function clear(); public function deleteItem($key); public function deleteItems(array $keys); public function save(CacheItemInterface $item); public function saveDeferred(CacheItemInterface $item); public function commit(); } ?></code>
CacheItemInterface
This represents a single item in the cache:
<code class="language-php"><?php namespace Psr\Cache; interface CacheItemInterface { public function getKey(); public function get(); public function isHit(); public function set($value); public function expiresAt($expiration); public function expiresAfter($time); } ?></code>
Here’s a practical example from our code base:
<code class="language-php"><?php namespace JonesRussell\PhpFigGuide\PSR6; use Psr\Cache\CacheItemInterface; use DateTimeInterface; use DateInterval; use DateTime; class CacheItem implements CacheItemInterface { private $key; private $value; private $isHit; private $expiration; public function __construct(string $key) { $this->key = $key; $this->isHit = false; } 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(?DateTimeInterface $expiration): self { $this->expiration = $expiration; return $this; } public function expiresAfter($time): self { if ($time instanceof DateInterval) { $this->expiration = (new DateTime())->add($time); } elseif (is_int($time)) { $this->expiration = (new DateTime())->add(new DateInterval("PT{$time}S")); } else { $this->expiration = null; } return $this; } // Helper method for our implementation public function getExpiration(): ?DateTimeInterface { return $this->expiration; } // Helper method for our implementation public function setIsHit(bool $hit): void { $this->isHit = $hit; } }</code>
<code class="language-php"><?php namespace JonesRussell\PhpFigGuide\PSR6; use Psr\Cache\CacheItemPoolInterface; use Psr\Cache\CacheItemInterface; use RuntimeException; class FileCachePool implements CacheItemPoolInterface { private $directory; private $deferred = []; 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 { $this->validateKey($key); if (isset($this->deferred[$key])) { return $this->deferred[$key]; } $item = new CacheItem($key); $path = $this->getPath($key); if (file_exists($path)) { try { $data = unserialize(file_get_contents($path)); if (!$data['expiration'] || $data['expiration'] > new DateTime()) { $item->set($data['value']); $item->setIsHit(true); return $item; } unlink($path); } catch (\Exception $e) { // Log error and continue with cache miss } } return $item; } public function getItems(array $keys = []): iterable { $items = []; foreach ($keys as $key) { $items[$key] = $this->getItem($key); } return $items; } public function hasItem($key): bool { return $this->getItem($key)->isHit(); } public function clear(): bool { $this->deferred = []; $files = glob($this->directory . '/*.cache'); if ($files === false) { return false; } $success = true; foreach ($files as $file) { if (!unlink($file)) { $success = false; } } return $success; } public function deleteItem($key): bool { $this->validateKey($key); unset($this->deferred[$key]); $path = $this->getPath($key); if (file_exists($path)) { return unlink($path); } return true; } public function deleteItems(array $keys): bool { $success = true; foreach ($keys as $key) { if (!$this->deleteItem($key)) { $success = false; } } return $success; } public function save(CacheItemInterface $item): bool { $path = $this->getPath($item->getKey()); $data = [ 'value' => $item->get(), 'expiration' => $item->getExpiration() ]; try { if (file_put_contents($path, serialize($data)) === false) { return false; } return true; } catch (\Exception $e) { return false; } } public function saveDeferred(CacheItemInterface $item): bool { $this->deferred[$item->getKey()] = $item; return true; } public function commit(): bool { $success = true; foreach ($this->deferred as $item) { if (!$this->save($item)) { $success = false; } } $this->deferred = []; return $success; } private function getPath(string $key): string { return $this->directory . '/' . sha1($key) . '.cache'; } private function validateKey(string $key): void { if (!is_string($key) || preg_match('#[{}()/@:\\]#', $key)) { throw new InvalidArgumentException( 'Invalid key: ' . var_export($key, true) ); } } }</code>
Let’s see how to use it in real code:
<code class="language-php">// 基本用法 $pool = new FileCachePool('/path/to/cache'); try { // 存储值 $item = $pool->getItem('user.1'); if (!$item->isHit()) { $userData = $database->fetchUser(1); // 您的数据库调用 $item->set($userData) ->expiresAfter(3600); // 1 小时 $pool->save($item); } $user = $item->get(); } catch (\Exception $e) { // 优雅地处理错误 log_error('缓存操作失败:' . $e->getMessage()); $user = $database->fetchUser(1); // 回退到数据库 }</code>
Tomorrow, we will discuss PSR-7 (HTTP Message Interface). If you're interested in simpler caching, stay tuned for our upcoming PSR-16 (Simple Caching) article, which offers a more straightforward alternative to PSR-6.
The above is the detailed content of PSR-Caching Interface in PHP. For more information, please follow other related articles on the PHP Chinese website!