PSR 6 缓存接口
缓存是提升应用性能的常用手段,为框架中最通用的功能,每个框架也都推出专属的、功能多样的缓存库。这些差别使得开发人员不得不学习多种系统,而很多可能是他们并不需要的功能。此外,缓存库的开发者同样面临着一个窘境,是只支持有限数量的几个框架还是创建一堆庞 大的适配器类。
一个通用的缓存系统接口可以解决这类问题:库和框架的开发人员可以期待缓存系统以它们想要的方式工作,而缓存系统的开发人员只需要实现一组接口而不是各式各样的是适配器
本篇规范中的 必须,不得,需要,应,不应,应该,不应该,推荐,可能 和 可选 等词按照 RFC 2119 中的描述进行解释
目标
此 PSR 的目标是:创建一套通用的接口规范,能够让开发人员整合到现有框架和系统,而不需要去开发框架专属的适配器类。
术语
-
「调用库」( Calling Library )
需要缓存服务的库或代码。这个库可以自由使用实现了此标准接口的缓存服务,而不需要知道这些缓存服务具体是如何实现的
-
「实现库」( Implementing Library )
实现了此缓存标准的类库,可以为任何 「调用库」提供缓存服务
实现库 必须 公开实现了
Cache\CacheItemPoolInterface
和Cache\ CacheItemInterface
接口的类实现库 必须 支持最低粒度的
TTL
功能,如下所述,也就是使用秒为单位的粒度 -
「存活时间」( TTL )
TTL 是
Time To Live
的缩写。一个项目 ( item ) 的 TTL 是指存储该项目的时间和过期时间之间的时间间隔。
TTL 通常使用以秒为单位的时间的整数或 DateInterval 对象来表示
-
「过期时间」( Expiration )
过期时间是项目 ( item ) 设置为过期的实际时间, 通常是项目的存储时间加上 TTL 计算得到的,但也可以显式的使用 DateTime 对象来设置。 一个在 1:30:00 存储的有着 300 秒 TTL 的项目 ( item ) 的过期时间是 1:35:00.
实现类库 可以 在项目要求的过期时间之前就将其过期,但只要达到过期时间,则 必须 视为过期。
如果调用库在保存项目时没有指定过期时间,或者指定了一个空的过期时间或 TTL,那么实现库 可以 使用配置的默认持续时间 ( TTL )
如果没有设置默认持续时间,那么实现库 必须 将其视为永久缓存该项目的请求,或者交给底层实现自行处理
-
「键」( Key )
键是由至少一个字符组成的,能够唯一标识缓存项的字符串
实现库 必须 支持 UTF-8 编码格式的,长度最多为 64 个字符组成的,由任意顺序的包括
A-Z
,a-z
,0-9
,_
和.
的字符组成的键实现库 可以 支持其它的字符或编码或更长的长度,但必须支持最小值
库可以自由决定如何转译键中的字符串,但 必须 能够返回原始的未修改的键
保留以下字符用于将来的扩展,实现类库绝对不能支持包含它们的任何键名
{} () / \ @ :
-
「命中」( Hit )
缓存命中是指调用库通过键来请求一个项目 ( item ) ,且找到了匹配的值情况。该值必须是未过期的,且不会因为某种其它原因而无效。
调用库 应该 确保在所有的
get()
调用中验证isHit()
-
「丢失」( miss )
缓存丢失与缓存命中相反。
缓存丢失是指调用库通过键来请求一个项目 ( item ),没找到匹配值的情况。没有匹配的原因可能是缓存项不存在,或者找到了缓存项但已经过期,或者其它原因导致缓存项无效。
已过期的缓存项 必须 始终视为缓存丢失
-
「 延迟 」( Deferred )
一个延迟的缓存保存动作用于指示缓存池 ( pool ) 可以不用立即持久化 ( 保存 ) 缓存项。
为了利用存储引擎支持的批量设置操作,一个缓存对象 可以 延迟持久化一个延迟的缓存项目
缓存池 必须 确保任何延迟的缓存项目最终都会被持久化而不会丢失数据,也 可以 在调用库请求它们持久化之前先进行持久化动作
只要调用库运行
commit()
方法,所有未缓存的延迟缓存项目都 必须 被持久化。实现库 可以 自由使用任何方法来确定何时应该持久化延迟的缓存项,例如在对象的析构函数中、在调用
save()
时持久化所有数据、超时、超过了最大的变更项目数或其它的合适的逻辑。获取延迟的缓存项目 必须 返回延迟的但尚未持久化的项目
数据
实现库 必须 支持所有可序列化的 PHP 数据类型,包括 :
-
- 字符串 ( Strings )
- PHP 兼容的任意编码的任意大小的字符串
-
- 整数 ( Integers )
- PHP 支持的任意大小的所有整数,最高支持 64 位有符号整型
-
- 浮点数 ( Floats )
- 所有有符号的浮点数
-
- 布尔类型 ( Boolean )
TRUE
和FALSE
-
- NULL 值 ( Null )
- 实际意义上的空值
NULL
-
- 数组 ( Arrays )
- 索引数组、关联数组、和任意维度的多维数组
-
- 对象 ( Object )
- 任何支持无损序列化和反序列化的对象,例如
$o == unserialize(serialize($o))
- 对象可以使用 PHP 的序列化接口、
__sleep()
或__wakeup()
所有传递给实现库的数据 必须 原样返回,包括变量类型。 也就是说,如果保存时的数据为 (int) 5,而返回时的数据为 ( string ) 5 ,那么就是错误的。
实现库可以在内部使用 PHP 的 serialize()
/ unserialize()
函数 ,但并不强制要求这么做,但与它们保持兼容性是对可接受对象的一个基准要求
如果出于任何原因不能返回保存时的原值,实现库 必须 返回缓存丢失而不是损坏了的数据
关键概念
「 缓存池 」 ( 池,Pool )
原本应该翻译为 池,但一个字,中文不太好读,于是加上了缓存两字
缓存池是缓存系统中的项目 ( item ) 的集合,缓存池是其包含的所有项目的逻辑存储库 ( 相比于持久化存储 ( 物理存储 ) 来说 )。
缓存池中所有可缓存的项目都可以作为项目对象 ( Item object ) 被检索
所有与缓存的对象的所有交互都必须通过缓存池来进行
「 项目 」 ( Items )
项目是缓存池中的单个键/值对
键 ( key ) 是项目的主要唯一标识符,且 必须 是不可变更的。
值则 可以 随时改变
错误处理
尽管缓存通常是应用程序性能的重要组成部分,但它永远不应该是应用程序功能的关键部分。
因此,缓存系统中的错误不应导致应用程序崩溃。 出于这个原因,实现库 不应该 抛出标准规范定义的异常以外的任何其它异常,并且 应该 捕获由底层数据存储触发的任何错误或异常,并且不允许它们冒泡
实现类库 应该 记录这些错误或适当时报告给系统管理员
如果调用库请求一个或多个被删除了的项目,或从已经清空了的缓存池中请求一个或多个项目,如果不存在请求的键,则 绝对不能 将将其视为错误条件,因为后验条件是一样的 ( 键不存在或缓存池为空 ),因此不存在错误情况
后验条件,好吧,又造了一个名词,以后有机会再解释吧。
接口
CacheItemInterface
CacheItemInterface 接口定义了缓存系统内的项目 ( item )
每个项目对象都 必须 与一个特定的键相关联,该键可根据实现系统进行设置,并且通常由 Cache\ CacheItemPoolInterface
对象传递。
Cache\CacheItemInterface
对象封装了缓存项目的存储和索引。
任何 Cache\CacheItemInterface
都由一个 Cache\CacheItemPoolInterface
对象生成,该对象负责所有必需的设置以及将 Cache\CacheItemInterface
对象关联到唯一的键。
Cache\CacheItemInterface
对象必须能够存储和检索本文档的数据部分中定义的任何类型的 PHP 值
调用库 绝对不能 自己实例化项目对象。
项目对象只能通过 getItem()
方法从缓存池对象中请求。
同时,调用库 不应该 假设一个实现库创建的项目对象和另一个实现库创建的项目对象相兼容。
<?php namespace Psr\Cache; /** * CacheItemInterface 定义了一个用于与缓存内的对象进行交互的接口 */ interface CacheItemInterface { /** * 返回当前缓存项的键 * * 键由实现库加载,但在需要时应提供给更高级别的调用者 * * @return string 当前缓存项的键 */ public function getKey(); /** * * 根据关联的对象的键从缓存中获取项目 ( item ) 的值 * * 返回的值 **必须** 和调用 `set()` 方法保存时传递的值相同 * * 如果 `isHit()` 返回 false,该方法 **必须** 返回 null。 * 注意: null 是合法的缓存值,可以使用 `isHit()` 方法来区分 "找到了一个 null 值" 和 "找不到值" * * * @return mixed * 与当前缓存项的键相对应的值,如果未找到,则返回 null */ public function get(); /** * Confirms if the cache item lookup resulted in a cache hit. * * 确认查找缓存项的结果是否为缓存命中 * * 注意:传递给此方法的条件 **必须** 和传递给 `get()` 方法的条件一样 * * @return bool * 如果请求的结果是缓存命中则返回 true ,否则返回 false */ public function isHit(); /** * 保存此缓存项的值 * * * $value 参数可以是任何可以被 PHP 序列化的项目,序列化的方法由实现库自己自由决定 * * @param mixed $value 需要被存储的可序列化的值 * * @return static 调用的对象 */ public function set($value); /** * 为此缓存项设置过期时间 * * @param \DateTimeInterface|null $expiration * 该时间点之后该项目必须被视为过期。 * 如果显式传递 null,表示可以使用默认值。 * 如果没有设置,值应该永久存储或实现类库自己决定 * * @return static 调用对象 * The called object. */ public function expiresAt($expiration); /** * 为此缓存项设置过期时间 * * @param int|\DateInterval|null $time * 从当前时间起该项目必须被视为过期的时间段。 * 整数参数被视为以秒为单位的时间,直到过期。 * 如果显式传递 null,可以使用默认值。 * 如果没有设置,值应该永久存储或实现类库自己决定 * * @return static 调用对象 */ public function expiresAfter($time); }
CacheItemPoolInterface
Cache\CacheItemPoolInterface
接口的主要目的是接受来自调用库的键并返回关联的 Cache\ CacheItemInterface
对象。
它也是与整个缓存集合交互的枢纽。
实现库可以自行决定缓存池的所有配置和如何初始化。
<?php namespace Psr\Cache; /** * CacheItemPoolInterface 生成 CacheItemInterface 对象 */ interface CacheItemPoolInterface { /** * 根据指定的键返回一个缓存项 ( Cache Item ) * * 此方法 **必须** 始终返回一个 CacheItemInterface 对象, * 即使是缓存丢失,也 **绝对不能** 返回 null * * @param string $key * 要返回的缓存项的键 * * @throws InvalidArgumentException * 如果 $key 不是一个合法的值, * 则 **必须** 抛出 \Psr\Cache\InvalidArgumentException 异常 * * @return CacheItemInterface * 相应的缓存项 */ public function getItem($key); /** * 返回一组可遍历的缓存项 * * @param string[] $keys * 要检索的项目的键的组成的索引数组 * * @throws InvalidArgumentException * 如果其中的任意某个键不是一个合法的值, * 则 **必须** 抛出 \Psr\Cache\InvalidArgumentException 异常 * * @return array|\Traversable * 由传递的每个键关联的的缓存项目组成的可遍历集合。 * 每个键都会返回一个缓存项,即使没有找到该键对应的项。 * 但是,如果没有指定任何键,则必须返回一个空的可遍历的对象。 */ public function getItems(array $keys = array()); /** * 确认缓存系统是否包含指定的缓存项目 * * 注意:出于性能原因,此方法 **可以** 避免检索缓存的值。 * 这可能会导致相同的条件下,与 `CacheItemInterface::get()` 的结果不一样 ( 是否存在 ) * 为了避免这种情况,可以使用 `CacheItemInterface::isHit()` 代替 * * * @param string $key * 要检查的键 * * @throws InvalidArgumentException * 如果 $key 不是一个合法的值,则 **必须** 抛出 \Psr\Cache\InvalidArgumentException 异常 * * @return bool * 如果缓存中存在项目,返回 true ,否则返回 false */ public function hasItem($key); /** * 清空缓存池 * * @return bool * 如果缓存池成功清空返回 true,否则返回 false */ public function clear(); /** * 从缓冲池里移除某个缓存项 * * @param string $key * 要删除的缓存项的键名 * * @throws InvalidArgumentException * 如果 $key 非法,则 **必须** 抛出 \Psr\Cache\InvalidArgumentException 异常 * * @return bool * 成功返回 true,有错误发生返回 false */ public function deleteItem($key); /** * 从缓存池中删除多个缓存项 * * @param string[] $keys * 要删除的缓存项的键名组成的数组 * @throws InvalidArgumentException * 如果 $key 中的任何一个值不合法,则 **必须** 抛出 \Psr\Cache\InvalidArgumentException 异常 * * @return bool * 成功返回 true,有错误发生返回 false */ public function deleteItems(array $keys); /** * 立即持久化 「CacheItemInterface」对象 * * @param CacheItemInterface $item * 将要被存储的缓存项 * * @return bool * 如果缓存项成功被持久化则返回 true,其它情况返回 false */ public function save(CacheItemInterface $item); /** * 延迟持久化 「CacheItemInterface」对象 * * @param CacheItemInterface $item * 将要被存储的缓存项 * * @return bool * 如果缓存项加入队列失败或尝试一个提交操作失败则返回 False,其它情况返回 true */ public function saveDeferred(CacheItemInterface $item); /** * 提交所有的正在队列里等待的请求到数据持久层,配合 `saveDeferred()` 使用 * * @return bool * 成功返回 true,有错误发生返回 false */ public function commit(); }
CacheException
异常接口用于严重错误发生的时候,包括但不限于缓存设置错误,例如:无法连接到缓存服务器、提供了无效的凭证。
实现类库抛出的任何异常都 必须 实现该接口
<?php namespace Psr\Cache; /** * 缓存异常接口,实现类库抛出的所有异常的根接口 * */ interface CacheException { }
InvalidArgumentException
<?php namespace Psr\Cache; /** * 无效缓存参数的异常接口 * * 任何时候,一个无效参数传递到方法时,必须抛出一个实现了 * Psr\Cache\InvalidArgumentException 的异常类。 * */ interface InvalidArgumentException extends CacheException { }