首页 >后端开发 >php教程 >PHP 中的 PSR 缓存接口

PHP 中的 PSR 缓存接口

DDD
DDD原创
2025-01-11 16:05:43982浏览

PSR-Caching Interface in PHP

大家好!

您的应用程序是否因为重复的数据库查询而运行缓慢?或者在不同的缓存库之间切换时遇到困难?让我们深入探讨 PSR-6,这个标准使 PHP 中的缓存变得可预测且可互换!

本文是 PHP PSR 标准系列的一部分。如果您是新手,不妨从 PSR-1 基础知识开始。

PSR-6 解决什么问题?(2 分钟)

在 PSR-6 之前,每个缓存库都有自己独特的工作方式。想要从 Memcached 切换到 Redis?重写您的代码。从一个框架迁移到另一个框架?学习新的缓存 API。PSR-6 通过提供所有缓存库都可以实现的通用接口来解决这个问题。

核心接口(5 分钟)

让我们来看看两个主要参与者:

1. CacheItemPoolInterface

这是您的缓存管理器。可以将其视为一个仓库,您可以在其中存储和检索项目:

<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>

2. CacheItemInterface

这表示缓存中的单个项目:

<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>

实际应用(10 分钟)

以下是我们代码库中的一个实际示例:

1. 缓存项实现

<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>

2. 缓存池实现

<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>

实际使用(5 分钟)

让我们看看如何在实际代码中使用它:

<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>

常见陷阱(3 分钟)

  1. 键验证
  2. 错误处理

下一步是什么?

明天,我们将讨论 PSR-7(HTTP 消息接口)。如果您对更简单的缓存感兴趣,请继续关注我们即将推出的 PSR-16(简单缓存)文章,该文章提供了比 PSR-6 更直接的替代方案。

资源

  • 官方 PSR-6 规范
  • 我们的示例代码库 (v0.6.0 - PSR-6 实现)
  • Symfony 缓存组件
  • PHP 缓存

以上是PHP 中的 PSR 缓存接口的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn