PHP项目缓存接口与适配器

wen PHP项目 2

本文目录导读:

PHP项目缓存接口与适配器

  1. 缓存接口设计
  2. 适配器实现
  3. 装饰器实现
  4. 缓存管理器
  5. 使用示例

我来详细讲解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);

这种设计模式的优势:

  1. 接口统一: 所有缓存驱动实现相同的接口
  2. 切换灵活: 可以轻松切换缓存驱动
  3. 扩展性好: 容易添加新的缓存驱动
  4. 装饰器模式: 可以添加横切关注点(日志、标签等)
  5. 依赖注入友好: 可以通过构造函数注入

这种架构非常适合在需要使用不同缓存方案的PHP项目中使用。

抱歉,评论功能暂时关闭!