talesoft/tale-cache-core

PSR-6 (缓存) 和 PSR-16 (SimpleCache) 入门包

0.1.0 2019-01-21 17:46 UTC

This package is auto-updated.

Last update: 2024-08-26 07:02:39 UTC


README

Packagist License CI Coverage

Tale Cache Core

什么是 Tale Cache Core?

Tale Cache Core 是 PSR-6 和 PSR-16 缓存标准的基本扩展,将它们合并为一个库。

它作为库的基础,使其与 PSR-6 和 PSR-16 缓存兼容,无需依赖重依赖,同时也作为 Tale Cache 库的基础。

此外,它试图解决标准 PSR 缓存规范中的单个问题。

安装

composer require talesoft/tale-cache-core

用法

PSR-6 到 PSR-16 适配器

使用 Tale\Cache\PoolCache,轻松在您的应用程序或库中使用 PSR-6 缓存池,即使它们偏好 PSR-16。

use Tale\Cache\PoolCache;

$pool = new RedisCachePool(); //Create some PSR-6 CacheItemPool

$cache = new PoolCache($pool);
//$cache is now a PSR-16 cache

$data = $cache->get('some.key');
if ($data === null) {

    $data = ...; //Generate $data somehow
    $cache->set('some.key', $data);
}

//$data is now a cached value

为库作者提供的空缓存和运行时缓存

有时库作者希望使他们的库具有缓存兼容性,但不想实现一个完整的缓存实现。虽然接口对此非常有效,但它们只能作为可选依赖项表示,而有时您真正想要的是对缓存或测试模拟实现的必需依赖项。真正的实现还避免了需要使您的属性为可空或对可选依赖项进行空检查的需要,因此您避免了大量的带有空检查的防御性编程。

Tale Cache Core 提供了两个轻量级、简单的 PSR-6 缓存池实现,可以用作默认值,就像正常缓存一样工作,只是它们实际上并没有做任何事情。

想象一个看起来像这样的服务

use Psr\Cache\CacheItemPoolInterface;

final class MyService
{
    /** @var CacheItemPoolInterface */
    private $cachePool;
    
    public function __construct(CacheItemPoolInterface $cachePool)
    {
        $this->cachePool = $cachePool;
    }
    
    public function getCachePool(): CacheItemPoolInterface
    {
        return $this->cachePool;
    }
    
    public function doStuff(): void
    {
        $metadataItem = $this->cachePool->getItem('metadata');
        if (!$metadataItem->isHit()) {
            $metadataItem
                ->expiresAfter(new \DateInterval('P2D'))
                ->set($this->generateHeavyMetadata());
                
            $this->cachePool->save($metadataItem);
        }
        
        $metadata = $metadataItem->get();
        //Do something with $metadata
    }
}

如果您可能想对此进行测试或在某处实例化它,您可以使用 NullPool

use Tale\Cache\Pool\NullPool;

$myService = new MyService(new NullPool());

$myService->doStuff();

这将基本上就像一个完全禁用的缓存一样工作。

如果您想有一些运行时缓存,以便缓存项不会反复生成,您也可以使用 RuntimePool,它将在进程存在的时间内缓存值。

use Tale\Cache\Pool\RuntimePool;

$myService = new MyService(new RuntimePool());

$myService->doStuff();
$myService->doStuff(); //This will be faster, as values are cached during runtime

如果您仍然想要可选依赖项,但想避免在库代码的各个地方进行防御性空检查,您只需使用空合并运算符将值默认为空。

public function __construct(CacheItemPoolInterface $cachePool = null)
{
    $this->cachePool = $cachePool ?? new NullPool();
}

轻松实现自定义缓存池

Tale Cache Core 在您想要编写 PSR-6 兼容缓存池时,为您减少了一些工作。我们将通过 Tale Cache Core 实现自己的文件缓存作为示例。

use Tale\Cache\Pool\AbstractPool;
use Tale\Cache\Item;

final class FilePool extends AbstractPool
{
    /** @var string */
    private $directory;
    
    public function __construct(string $directory)
    {
        $this->directory = $directory;
    }
    
    public function getItem($key): ItemInterface
    {
        $path = $this->getPathFromKey($key);
        if (file_exists($path)) {
        
            //Unserialize data from file
            [$ttl, $value] = unserialize(file_get_contents($path));
            
            //Check TTL
            if (time() < filemtime() + $ttl) {
            
                $expirationTime = new \DateTimeImmutable();
                $expirationTime->setTimestamp($expirationTime->getTimestamp() + $ttl);
                
                //Return a hit item
                return Item::createHit($key, $value, $expirationTime);
            }
        }
        //Create a miss
        return Item::createMiss($key);
    }

    public function clear(): bool
    {
        $files = glob("{$this->directory}/*.cache");
        $success = true;
        foreach ($files as $file) {
            if (!unlink($file)) {
                $success = false;
            }
        }
        return $success;
    }

    public function deleteItem($key): bool
    {
        $path = $this->getPathFromKey($key);
        return unlink($path);
    }

    public function save(CacheItemInterface $item): bool
    {
        //Make sure that it's an (interopable) Tale\Cache\ItemInterface instance
        //including items from this instance (which makes it downwards PSR-6 compatible)
        $this->filterItem($item);
        
        $path = $this->getPathFromKey($item->getKey());
        
        //This ->getExpireTime() call here is what enables interopability
        $ttl = time() - $item->getExpireTime()->getTimestamp();
        
        return file_put_contents($path, serialize([$ttl, $item->get()])) !== false;
    }
    
    private function getPathFromKey($key): string
    {
        $hash = md5($key);
        return "{$this->directory}/{$hash}.cache";
    }
}

现在您有一个完全有效的、使用文件的 PSR-6 缓存。

$pool = new FilePool('/my/cache');

$item = $pool->getItem('my.data');
if (!$item->isHit()) {
    //Generate $data somehow
    $data = ...;
    
    $item
        ->expiresAfter(new \DateInterval('P2D'))
        ->set($data);
    $pool->saveDeferred($item);
}

$cachedData = $item->get();

//At end of execution
$pool->commit();

可互操作缓存项

Tale Cache Core 扩展了正常的 PSR-6/16 接口,并添加了一个方法,提供对所有可能的 CachePools 使用单个 CacheItem 实现的能力。

针对代码的新接口(所有都是 PSR-6/16 兼容的)

Psr\SimpleCache\CacheInterface   => Tale\CacheInterface
    | No new methods
    
Psr\Cache\CacheItemPoolInterface => Tale\Cache\PoolInterface
    | getItem($key): Tale\Cache\ItemInterface (type narrowing)
    
Psr\Cache\CacheItemInterface     => Tale\Cache\ItemInterface
    | getExpirationTime(): ?DateTimeInterface

如您所见,Tale\Cache\ItemInterface 为接口提供了单个 方法,允许我们 检索 缓存项的指定过期时间。这使得 Tale\Cache\ItemInterface 能够在不同项池实现之间完全互操作。

您可以将项从一个池移动到另一个池:

$poolA = new SomeCachePool();
$poolB = new SomeOtherCachePool();

$item = $poolA->getItem('some.item');
if ($item->isHit()) {
    $poolA->deleteItem($item);
    $poolB->save($item); //Cache Item has been moved to poolB
}

这是可能的,因为缓存项最终可以是一个普通的 DTO,不需要其池设置其过期时间,缓存的值存储在项内部,包括其键和 TTL,因此它可以始终移动或复制到其他项池。