缓存是提高任何项目性能的常用方法,使缓存库成为许多框架和库的最常见功能之一。这导致了许多库推出自己的缓存库,具有各种级别的功能。这些差异导致开发人员必须学习多个系统,这些系统可能会也可能不会提供他们所需的功能。此外,缓存库本身的开发人员面临着只支持有限数量的框架或创建大量适配器类的选择。
缓存系统的通用接口将解决这些问题。库和框架开发人员可以按照他们期望的方式依赖缓存系统,而缓存系统的开发人员只需要实现一组接口而不是各种各样的适配器。
此外,此处介绍的实现是为了将来的可扩展性而设计的。它允许各种内部不同但API兼容的实现,并为以后的PSR或特定实现者提供了明确的未来扩展途径。
优点:
缺点:
该规范采用“存储库模型”或“数据映射器”模型进行缓存,而不是更传统的“可过期键值”模型。主要原因是灵活性。简单的键/值模型更难以扩展。
此处的模型要求使用CacheItem对象和Pool对象,CacheItem对象表示缓存条目,Pool对象是缓存数据的给定存储。从池中检索项目,与之交互并返回到项目。虽然有时更冗长,但它提供了一种良好,强大,灵活的缓存方法,特别是在缓存比简单地保存和检索字符串更复杂的情况下。
大多数方法名称是根据成员项目和其他流行的非成员系统调查中的通用实践和方法名称选择的。
优点:
缺点:
例子:
一些常见的使用模式如下所示。这些都是非规范性的,但应该证明一些设计决策的应用。
/**
* Gets a list of available widgets.
*
* In this case, we assume the widget list changes so rarely that we want
* the list cached forever until an explicit clear.
*/
function get_widget_list()
{
$pool = get_cache_pool('widgets');
$item = $pool->getItem('widget_list');
if (!$item->isHit()) {
$value = compute_expensive_widget_list();
$item->set($value);
$pool->save($item);
}
return $item->get();
}
/**
* Caches a list of available widgets.
*
* In this case, we assume a list of widgets has been computed and we want
* to cache it, regardless of what may already be cached.
*/
function save_widget_list($list)
{
$pool = get_cache_pool('widgets');
$item = $pool->getItem('widget_list');
$item->set($list);
$pool->save($item);
}
/**
* Clears the list of available widgets.
*
* In this case, we simply want to remove the widget list from the cache. We
* don't care if it was set or not; the post condition is simply "no longer set".
*/
function clear_widget_list()
{
$pool = get_cache_pool('widgets');
$pool->deleteItems(['widget_list']);
}
/**
* Clears all widget information.
*
* In this case, we want to empty the entire widget pool. There may be other
* pools in the application that will be unaffected.
*/
function clear_widget_cache()
{
$pool = get_cache_pool('widgets');
$pool->clear();
}
/**
* Load widgets.
*
* We want to get back a list of widgets, of which some are cached and some
* are not. This of course assumes that loading from the cache is faster than
* whatever the non-cached loading mechanism is.
*
* In this case, we assume widgets may change frequently so we only allow them
* to be cached for an hour (3600 seconds). We also cache newly-loaded objects
* back to the pool en masse.
*
* Note that a real implementation would probably also want a multi-load
* operation for widgets, but that's irrelevant for this demonstration.
*/
function load_widgets(array $ids)
{
$pool = get_cache_pool('widgets');
$keys = array_map(function($id) { return 'widget.' . $id; }, $ids);
$items = $pool->getItems($keys);
$widgets = array();
foreach ($items as $key => $item) {
if ($item->isHit()) {
$value = $item->get();
} else {
$value = expensive_widget_load($id);
$item->set($value);
$item->expiresAfter(3600);
$pool->saveDeferred($item, true);
}
$widget[$value->id()] = $value;
}
$pool->commit(); // If no items were deferred this is a no-op.
return $widgets;
}
/**
* This examples reflects functionality that is NOT included in this
* specification, but is shown as an example of how such functionality MIGHT
* be added by extending implementations.
*/
interface TaggablePoolInterface extends Psr\Cache\CachePoolInterface
{
/**
* Clears only those items from the pool that have the specified tag.
*/
clearByTag($tag);
}
interface TaggableItemInterface extends Psr\Cache\CacheItemInterface
{
public function setTags(array $tags);
}
/**
* Caches a widget with tags.
*/
function set_widget(TaggablePoolInterface $pool, Widget $widget)
{
$key = 'widget.' . $widget->id();
$item = $pool->getItem($key);
$item->setTags($widget->tags());
$item->set($widget);
$pool->save($item);
}
各种早期的草案采用了更为简单的“关键值到期”方法,也称为“弱项”方法。在这个模型中,“Cache Item”对象实际上只是一个带有方法的dumb数组对象。用户将直接实例化它,然后将其传递给缓存池。虽然更熟悉,但这种方法有效地阻止了缓存项的任何有意义的扩展。它有效地使Cache Item的构造函数成为隐式接口的一部分,从而严重限制了可扩展性或使缓存项成为智能所在的能力。
在2013年6月进行的一项民意调查中,大多数参与者明显倾向于采用更为稳健的“强项目”/存储库方法,这种方法被采纳为前进方向。
优点:
缺点:
一些关于Cache规范的最早讨论建议一起跳过Cache Item概念,只是读取/写入要缓存的原始值。虽然更简单,但有人指出,无法区分缓存未命中和选择表示缓存未命中的原始值之间的区别。也就是说,如果缓存查找返回NULL,则无法判断是否没有缓存值,或者NULL是否是已缓存的值。(在很多情况下,NULL是一个合法的缓存值。)
我们审查的更强大的缓存实现 - 特别是Stash缓存库和Drupal使用的本地缓存系统 - get
至少使用某种结构化对象来避免miss和sentinel值之间的混淆。基于之前的经验,FIG决定裸体价值get
是不可能的。
有人建议使用Pool实现ArrayAccess,这将允许缓存获取/设置操作使用数组语法。由于兴趣有限,这种方法的灵活性有限(使用默认控制信息进行简单的获取和设置是可能的),这被拒绝了,并且因为如果想要这样做,将特定实现包含为附加组件是微不足道的。
注意:顺序按时间顺序递减。
该CacheItemInterface::expiresAt()
方法的$expiration
参数是在接口类型化,但在文档块被指定为\DateTimeInterface
。目的是允许一个\DateTime
或一个\DateTimeImmutable
对象。但是,\DateTimeInterface
并且\DateTimeImmutable
在PHP 5.5中添加,并且作者选择不对规范强加PHP 5.5的硬语法要求。
尽管如此,实现者必须只接受\DateTimeInterface
或兼容类型(例如\DateTime
和\DateTimeImmutable
),就好像方法是明确键入的一样。(请注意,键入参数的方差规则可能因语言版本而异。)
不幸的是,模拟失败的类型检查在PHP版本之间有所不同,因此不建议这样做。相反,实现者应该抛出一个实例\Psr\Cache\InvalidArgumentException
。
建议使用以下示例代码,以强制对expiresAt()方法进行类型检查:
class ExpiresAtInvalidParameterException implements Psr\Cache\InvalidArgumentException {}
// ...
if (! (
null === $expiration
|| $expiration instanceof \DateTime
|| $expiration instanceof \DateTimeInterface
)) {
throw new ExpiresAtInvalidParameterException(sprintf(
'Argument 1 passed to %s::expiresAt() must be an instance of DateTime or DateTimeImmutable; %s given',
get_class($this),
is_object($expiration) ? get_class($expiration) : gettype($expiration)
));
}