本文目录导读:

我来详细讲解PHP项目中的缓存接口与适配器设计模式。
缓存接口设计
基础缓存接口
<?php
namespace App\Cache;
interface CacheInterface
{
/**
* 获取缓存
* @param string $key 缓存键
* @param mixed $default 默认值
* @return mixed
*/
public function get(string $key, mixed $default = null): mixed;
/**
* 设置缓存
* @param string $key 缓存键
* @param mixed $value 缓存值
* @param int|null $ttl 过期时间(秒)
* @return bool
*/
public function set(string $key, mixed $value, ?int $ttl = null): bool;
/**
* 删除缓存
* @param string $key 缓存键
* @return bool
*/
public function delete(string $key): bool;
/**
* 清空所有缓存
* @return bool
*/
public function clear(): bool;
/**
* 判断缓存是否存在
* @param string $key 缓存键
* @return bool
*/
public function has(string $key): bool;
/**
* 批量获取缓存
* @param array<string> $keys 缓存键数组
* @return array
*/
public function getMultiple(array $keys): array;
/**
* 批量设置缓存
* @param array $values 键值对数组
* @param int|null $ttl 过期时间
* @return bool
*/
public function setMultiple(array $values, ?int $ttl = null): bool;
/**
* 批量删除缓存
* @param array<string> $keys 缓存键数组
* @return bool
*/
public function deleteMultiple(array $keys): bool;
}
高级缓存接口(可选)
<?php
namespace App\Cache;
interface AdvancedCacheInterface extends CacheInterface
{
/**
* 递增
* @param string $key 缓存键
* @param int $value 递增的值
* @return int|false
*/
public function increment(string $key, int $value = 1): int|false;
/**
* 递减
* @param string $key 缓存键
* @param int $value 递减的值
* @return int|false
*/
public function decrement(string $key, int $value = 1): int|false;
/**
* 获取并删除
* @param string $key 缓存键
* @return mixed
*/
public function pull(string $key): mixed;
/**
* 如果不存在则设置
* @param string $key 缓存键
* @param mixed $value 缓存值
* @param int|null $ttl 过期时间
* @return bool
*/
public function add(string $key, mixed $value, ?int $ttl = null): bool;
}
适配器实现
Redis适配器
<?php
namespace App\Cache\Adapter;
use App\Cache\AdvancedCacheInterface;
use Redis;
use RedisException;
class RedisAdapter implements AdvancedCacheInterface
{
private Redis $redis;
private string $prefix;
public function __construct(Redis $redis, string $prefix = 'cache:')
{
$this->redis = $redis;
$this->prefix = $prefix;
}
private function buildKey(string $key): string
{
return $this->prefix . $key;
}
public function get(string $key, mixed $default = null): mixed
{
try {
$value = $this->redis->get($this->buildKey($key));
return $value === false ? $default : unserialize($value);
} catch (RedisException $e) {
// 记录日志
return $default;
}
}
public function set(string $key, mixed $value, ?int $ttl = null): bool
{
try {
$key = $this->buildKey($key);
$value = serialize($value);
if ($ttl !== null) {
return $this->redis->setex($key, $ttl, $value);
}
return $this->redis->set($key, $value);
} catch (RedisException $e) {
return false;
}
}
public function delete(string $key): bool
{
try {
return $this->redis->del($this->buildKey($key)) > 0;
} catch (RedisException $e) {
return false;
}
}
public function clear(): bool
{
try {
// 只清除当前前缀的缓存
$keys = $this->redis->keys($this->prefix . '*');
if (!empty($keys)) {
return $this->redis->del($keys) > 0;
}
return true;
} catch (RedisException $e) {
return false;
}
}
public function has(string $key): bool
{
try {
return $this->redis->exists($this->buildKey($key));
} catch (RedisException $e) {
return false;
}
}
public function getMultiple(array $keys): array
{
try {
$prefixedKeys = array_map(fn($key) => $this->buildKey($key), $keys);
$values = $this->redis->mget($prefixedKeys);
$result = [];
foreach ($keys as $i => $key) {
$result[$key] = $values[$i] === false ? null : unserialize($values[$i]);
}
return $result;
} catch (RedisException $e) {
return [];
}
}
public function setMultiple(array $values, ?int $ttl = null): bool
{
try {
$this->redis->multi();
foreach ($values as $key => $value) {
$key = $this->buildKey($key);
$value = serialize($value);
if ($ttl !== null) {
$this->redis->setex($key, $ttl, $value);
} else {
$this->redis->set($key, $value);
}
}
return $this->redis->exec() !== false;
} catch (RedisException $e) {
return false;
}
}
public function deleteMultiple(array $keys): bool
{
try {
$prefixedKeys = array_map(fn($key) => $this->buildKey($key), $keys);
return $this->redis->del($prefixedKeys) > 0;
} catch (RedisException $e) {
return false;
}
}
public function increment(string $key, int $value = 1): int|false
{
try {
return $this->redis->incrBy($this->buildKey($key), $value);
} catch (RedisException $e) {
return false;
}
}
public function decrement(string $key, int $value = 1): int|false
{
try {
return $this->redis->decrBy($this->buildKey($key), $value);
} catch (RedisException $e) {
return false;
}
}
public function pull(string $key): mixed
{
try {
$value = $this->get($key);
if ($value !== null) {
$this->delete($key);
}
return $value;
} catch (RedisException $e) {
return null;
}
}
public function add(string $key, mixed $value, ?int $ttl = null): bool
{
try {
$key = $this->buildKey($key);
$value = serialize($value);
if ($ttl !== null) {
return $this->redis->set($key, $value, ['nx', 'ex' => $ttl]);
}
return $this->redis->setnx($key, $value);
} catch (RedisException $e) {
return false;
}
}
}
Memcached适配器
<?php
namespace App\Cache\Adapter;
use App\Cache\CacheInterface;
use Memcached;
class MemcachedAdapter implements CacheInterface
{
private Memcached $memcached;
private string $prefix;
public function __construct(Memcached $memcached, string $prefix = 'cache:')
{
$this->memcached = $memcached;
$this->prefix = $prefix;
}
private function buildKey(string $key): string
{
return $this->prefix . $key;
}
public function get(string $key, mixed $default = null): mixed
{
$value = $this->memcached->get($this->buildKey($key));
return $value === false ? $default : $value;
}
public function set(string $key, mixed $value, ?int $ttl = null): bool
{
$ttl = $ttl ?? 0;
return $this->memcached->set($this->buildKey($key), $value, $ttl);
}
public function delete(string $key): bool
{
return $this->memcached->delete($this->buildKey($key));
}
public function clear(): bool
{
return $this->memcached->flush();
}
public function has(string $key): bool
{
return $this->memcached->get($this->buildKey($key)) !== false;
}
public function getMultiple(array $keys): array
{
$prefixedKeys = array_map(fn($key) => $this->buildKey($key), $keys);
$values = $this->memcached->getMulti($prefixedKeys);
$result = [];
foreach ($keys as $key) {
$prefixedKey = $this->buildKey($key);
$result[$key] = isset($values[$prefixedKey]) ? $values[$prefixedKey] : null;
}
return $result;
}
public function setMultiple(array $values, ?int $ttl = null): bool
{
$prefixedValues = [];
foreach ($values as $key => $value) {
$prefixedValues[$this->buildKey($key)] = $value;
}
$ttl = $ttl ?? 0;
return $this->memcached->setMulti($prefixedValues, $ttl);
}
public function deleteMultiple(array $keys): bool
{
$prefixedKeys = array_map(fn($key) => $this->buildKey($key), $keys);
return $this->memcached->deleteMulti($prefixedKeys) !== false;
}
}
文件缓存适配器
<?php
namespace App\Cache\Adapter;
use App\Cache\CacheInterface;
class FileCacheAdapter implements CacheInterface
{
private string $cacheDir;
public function __construct(string $cacheDir = null)
{
$this->cacheDir = $cacheDir ?? sys_get_temp_dir() . '/cache';
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0777, true);
}
}
private function getFilePath(string $key): string
{
$hash = md5($key);
$subDir = substr($hash, 0, 2);
$dir = $this->cacheDir . '/' . $subDir;
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
return $dir . '/' . $hash . '.cache';
}
public function get(string $key, mixed $default = null): mixed
{
$file = $this->getFilePath($key);
if (!file_exists($file)) {
return $default;
}
$data = unserialize(file_get_contents($file));
// 检查是否过期
if ($data['ttl'] !== null && time() > $data['expires_at']) {
unlink($file);
return $default;
}
return $data['value'];
}
public function set(string $key, mixed $value, ?int $ttl = null): bool
{
$file = $this->getFilePath($key);
$data = [
'value' => $value,
'ttl' => $ttl,
'expires_at' => $ttl !== null ? time() + $ttl : null,
'created_at' => time()
];
return file_put_contents($file, serialize($data), LOCK_EX) !== false;
}
public function delete(string $key): bool
{
$file = $this->getFilePath($key);
if (file_exists($file)) {
return unlink($file);
}
return true;
}
public function clear(): bool
{
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($this->cacheDir, \RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($files as $file) {
if ($file->isFile() && $file->getExtension() === 'cache') {
unlink($file->getRealPath());
}
}
return true;
}
public function has(string $key): bool
{
$value = $this->get($key);
return $value !== null;
}
public function getMultiple(array $keys): array
{
$result = [];
foreach ($keys as $key) {
$result[$key] = $this->get($key);
}
return $result;
}
public function setMultiple(array $values, ?int $ttl = null): bool
{
foreach ($values as $key => $value) {
if (!$this->set($key, $value, $ttl)) {
return false;
}
}
return true;
}
public function deleteMultiple(array $keys): bool
{
foreach ($keys as $key) {
$this->delete($key);
}
return true;
}
}
装饰器实现
标签缓存装饰器
<?php
namespace App\Cache\Decorator;
use App\Cache\CacheInterface;
class TaggedCacheDecorator implements CacheInterface
{
private CacheInterface $cache;
private string $tagPrefix = 'tag:';
public function __construct(CacheInterface $cache)
{
$this->cache = $cache;
}
/**
* 设置带标签的缓存
*/
public function setWithTags(string $key, mixed $value, array $tags = [], ?int $ttl = null): bool
{
// 保存标签关系
foreach ($tags as $tag) {
$tagKey = $this->tagPrefix . $tag;
$taggedKeys = $this->cache->get($tagKey, []);
$taggedKeys[] = $key;
$this->cache->set($tagKey, array_unique($taggedKeys));
}
return $this->cache->set($key, $value, $ttl);
}
/**
* 清除指定标签的所有缓存
*/
public function clearByTag(string $tag): bool
{
$tagKey = $this->tagPrefix . $tag;
$taggedKeys = $this->cache->get($tagKey, []);
foreach ($taggedKeys as $key) {
$this->cache->delete($key);
}
$this->cache->delete($tagKey);
return true;
}
public function get(string $key, mixed $default = null): mixed
{
return $this->cache->get($key, $default);
}
public function set(string $key, mixed $value, ?int $ttl = null): bool
{
return $this->cache->set($key, $value, $ttl);
}
public function delete(string $key): bool
{
return $this->cache->delete($key);
}
public function clear(): bool
{
return $this->cache->clear();
}
public function has(string $key): bool
{
return $this->cache->has($key);
}
public function getMultiple(array $keys): array
{
return $this->cache->getMultiple($keys);
}
public function setMultiple(array $values, ?int $ttl = null): bool
{
return $this->cache->setMultiple($values, $ttl);
}
public function deleteMultiple(array $keys): bool
{
return $this->cache->deleteMultiple($keys);
}
}
日志装饰器
<?php
namespace App\Cache\Decorator;
use App\Cache\CacheInterface;
use Psr\Log\LoggerInterface;
class LoggingCacheDecorator implements CacheInterface
{
private CacheInterface $cache;
private LoggerInterface $logger;
public function __construct(CacheInterface $cache, LoggerInterface $logger)
{
$this->cache = $cache;
$this->logger = $logger;
}
public function get(string $key, mixed $default = null): mixed
{
$startTime = microtime(true);
$result = $this->cache->get($key, $default);
$elapsedTime = microtime(true) - $startTime;
$hit = $result !== $default;
$this->logger->info('Cache access', [
'key' => $key,
'hit' => $hit,
'time' => $elapsedTime
]);
return $result;
}
public function set(string $key, mixed $value, ?int $ttl = null): bool
{
$this->logger->info('Cache set', [
'key' => $key,
'ttl' => $ttl
]);
return $this->cache->set($key, $value, $ttl);
}
public function delete(string $key): bool
{
$this->logger->info('Cache delete', ['key' => $key]);
return $this->cache->delete($key);
}
public function clear(): bool
{
$this->logger->warning('Cache cleared');
return $this->cache->clear();
}
public function has(string $key): bool
{
return $this->cache->has($key);
}
public function getMultiple(array $keys): array
{
return $this->cache->getMultiple($keys);
}
public function setMultiple(array $values, ?int $ttl = null): bool
{
return $this->cache->setMultiple($values, $ttl);
}
public function deleteMultiple(array $keys): bool
{
return $this->cache->deleteMultiple($keys);
}
}
缓存管理器
<?php
namespace App\Cache;
use App\Cache\Adapter\RedisAdapter;
use App\Cache\Adapter\FileCacheAdapter;
use App\Cache\Decorator\TaggedCacheDecorator;
class CacheManager
{
private array $drivers = [];
private ?CacheInterface $defaultDriver = null;
public function __construct(array $config = [])
{
$this->initializeFromConfig($config);
}
private function initializeFromConfig(array $config): void
{
if (isset($config['redis'])) {
$this->addDriver('redis', new RedisAdapter(
new \Redis(),
$config['redis']['prefix'] ?? 'cache:'
));
}
if (isset($config['file'])) {
$this->addDriver('file', new FileCacheAdapter(
$config['file']['path'] ?? null
));
}
}
public function addDriver(string $name, CacheInterface $driver): void
{
$this->drivers[$name] = $driver;
if ($this->defaultDriver === null) {
$this->defaultDriver = $driver;
}
}
public function driver(?string $name = null): CacheInterface
{
if ($name === null) {
return $this->defaultDriver;
}
if (!isset($this->drivers[$name])) {
throw new \InvalidArgumentException("Cache driver '{$name}' not found");
}
return $this->drivers[$name];
}
public function tags(array $tags): TaggedCacheDecorator
{
return new TaggedCacheDecorator($this->defaultDriver);
}
public function __call(string $method, array $arguments): mixed
{
return $this->defaultDriver->$method(...$arguments);
}
}
使用示例
<?php
// 配置缓存
$config = [
'redis' => [
'prefix' => 'app:',
],
'file' => [
'path' => '/var/cache/'
]
];
// 创建缓存管理器
$cacheManager = new CacheManager($config);
// 使用默认驱动(Redis)
$cache = $cacheManager->driver();
// 基本使用
$cache->set('user:1', ['name' => 'John', 'age' => 30], 3600);
$user = $cache->get('user:1', ['name' => 'Guest']);
// 标签缓存
$taggedCache = $cacheManager->tags(['users', 'profiles']);
$taggedCache->setWithTags('user:1', $userData, ['users', 'active'], 3600);
// 批量操作
$cache->setMultiple([
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3'
], 1800);
$values = $cache->getMultiple(['key1', 'key2', 'key3']);
// 使用不同的驱动
$fileCache = $cacheManager->driver('file');
$fileCache->set('config', $config, 7200);
这种设计模式的优势:
- 接口统一: 所有缓存驱动实现相同的接口
- 切换灵活: 可以轻松切换缓存驱动
- 扩展性好: 容易添加新的缓存驱动
- 装饰器模式: 可以添加横切关注点(日志、标签等)
- 依赖注入友好: 可以通过构造函数注入
这种架构非常适合在需要使用不同缓存方案的PHP项目中使用。