PSR-6元文档

PSR-Cache元文档

1.总结

缓存是提高任何项目性能的常用方法,使缓存库成为许多框架和库的最常见功能之一。这导致了许多库推出自己的缓存库,具有各种级别的功能。这些差异导致开发人员必须学习多个系统,这些系统可能会也可能不会提供他们所需的功能。此外,缓存库本身的开发人员面临着只支持有限数量的框架或创建大量适配器类的选择。

为什么要打扰?

缓存系统的通用接口将解决这些问题。库和框架开发人员可以按照他们期望的方式依赖缓存系统,而缓存系统的开发人员只需要实现一组接口而不是各种各样的适配器。

此外,此处介绍的实现是为了将来的可扩展性而设计的。它允许各种内部不同但API兼容的实现,并为以后的PSR或特定实现者提供了明确的未来扩展途径。

优点:

  • 用于缓存的标准接口允许独立的库无需支付高速缓存中间数据的缓存; 他们可以简单地(可选地)依赖于这个标准接口并利用它而不用担心实现细节。
  • 多个项目共享的共同开发的缓存库,即使它们扩展了这个接口,也可能比十几个单独开发的实现更强大。

缺点:

  • 任何界面标准化都有可能扼杀未来的创新,因为它“不是它完成的方式”(tm)。但是,我们认为缓存是一个充分商品化的问题空间,此处提供的扩展功能可以减轻任何潜在的停滞风险。

3.范围

3.1目标

  • 基本和中级缓存需求的通用接口。
  • 一种明确的机制,用于扩展规范以支持高级功能,包括未来的PSR或单个实现。此机制必须允许多个独立扩展而不会发生冲突。

3.2非目标

  • 与所有现有缓存实现的架构兼容性。
  • 高级缓存功能,例如少数用户使用的命名空间或标记。

4.方法

4.1选择方法

该规范采用“存储库模型”或“数据映射器”模型进行缓存,而不是更传统的“可过期键值”模型。主要原因是灵活性。简单的键/值模型更难以扩展。

此处的模型要求使用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);
}

4.2替代方案:“弱项”方法

各种早期的草案采用了更为简单的“关键值到期”方法,也称为“弱项”方法。在这个模型中,“Cache Item”对象实际上只是一个带有方法的dumb数组对象。用户将直接实例化它,然后将其传递给缓存池。虽然更熟悉,但这种方法有效地阻止了缓存项的任何有意义的扩展。它有效地使Cache Item的构造函数成为隐式接口的一部分,从而严重限制了可扩展性或使缓存项成为智能所在的能力。

在2013年6月进行的一项民意调查中,大多数参与者明显倾向于采用更为稳健的“强项目”/存储库方法,这种方法被采纳为前进方向。

优点:

  • 更传统的方法。

缺点:

  • 不太可扩展或灵活。

4.3替代方案:“赤裸裸的价值”方法

一些关于Cache规范的最早讨论建议一起跳过Cache Item概念,只是读取/写入要缓存的原始值。虽然更简单,但有人指出,无法区分缓存未命中和选择表示缓存未命中的原始值之间的区别。也就是说,如果缓存查找返回NULL,则无法判断是否没有缓存值,或者NULL是否是已缓存的值。(在很多情况下,NULL是一个合法的缓存值。)

我们审查的更强大的缓存实现 - 特别是Stash缓存库和Drupal使用的本地缓存系统 - get至少使用某种结构化对象来避免miss和sentinel值之间的混淆。基于之前的经验,FIG决定裸体价值get是不可能的。

4.4替代方案:ArrayAccess池

有人建议使用Pool实现ArrayAccess,这将允许缓存获取/设置操作使用数组语法。由于兴趣有限,这种方法的灵活性有限(使用默认控制信息进行简单的获取和设置是可能的),这被拒绝了,并且因为如果想要这样做,将特定实现包含为附加组件是微不足道的。

5.人

5.1编辑

  • 拉里加菲尔德

5.2赞助商

  • Paul Dragoonis,PPI框架(协调员)
  • 罗伯特哈夫纳,斯塔什

6.投票

在邮件列表上接受投票

注意:顺序按时间顺序递减。

8.勘误表

8.1 expiresAt()中处理错误的DateTime值

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)
    ));
}