-
Notifications
You must be signed in to change notification settings - Fork 53
Added PSR-6 adapter #54
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
# Zend\\Cache\\Psr\\CacheItemPoolAdapter | ||
|
||
## Overview | ||
|
||
The `Zend\Cache\Psr\CacheItemPoolAdapter` provides a [PSR-6](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-6-cache.md) | ||
compliant wrapper for supported storage adapters. | ||
|
||
PSR-6 specifies a common interface to cache storage, enabling developers to switch between implementations without | ||
having to worry about any behind-the-scenes differences between them. | ||
|
||
|
||
## Quick Start | ||
|
||
To use the pool, instantiate your storage as normal, then pass it to the `CacheItemPoolAdapter`. | ||
|
||
```php | ||
use Zend\Cache\StorageFactory; | ||
use Zend\Cache\Psr\CacheItemPoolAdapter; | ||
|
||
$storage = StorageFactory::factory([ | ||
'adapter' => [ | ||
'name' => 'apc', | ||
'options' => [], | ||
], | ||
]); | ||
|
||
$pool = new CacheItemPoolAdapter($storage); | ||
|
||
// attempt to get an item from cache | ||
$item = $pool->getItem('foo'); | ||
|
||
// check whether item was found | ||
if (! $item->isHit()) { | ||
// ... | ||
// perform expensive operation to calculate $value for 'foo' | ||
// ... | ||
|
||
$item->set($value); | ||
$pool->save($item); | ||
} | ||
|
||
// use the value of the item | ||
echo $item->get(); | ||
``` | ||
|
||
Note that you will always get back a `CacheItem` object, whether it was found in cache or not: this is so `false`-y | ||
values like an empty string, `null`, or `false` can be stored. Always check `isHit()` to determine if the item was | ||
found. | ||
|
||
|
||
## Supported adapters | ||
|
||
The PSR-6 specification requires that the underlying storage support time-to-live (TTL), which is set when the | ||
item is saved. For this reason the following adapters cannot be used: `Dba`, `Filesystem`, `Memory` and `Session`. The | ||
`XCache` adapter calculates TTLs based on the request time, not the time the item is actually persisted, which means | ||
that it also cannot be used. | ||
|
||
In addition adapters must support the `Zend\Cache\FlushableInterface`. All the current `Zend\Cache\Storage\Adapter`s | ||
fulfil this requirement. | ||
|
||
Attempting to use an unsupported adapter will throw an exception implementing `Psr\Cache\CacheException`. | ||
|
||
### Quirks | ||
|
||
#### APC | ||
|
||
You cannot set the [`apc.use_request_time`](http://php.net/manual/en/apc.configuration.php#ini.apc.use-request-time) | ||
ini setting with the APC adapter: the specification requires that all TTL values are calculated from when the item is | ||
actually saved to storage. If this is set when you instantiate the pool it will throw an exception implementing | ||
`Psr\Cache\CacheException`. Changing the setting after you have instantiated the pool will result in non-standard | ||
behaviour. | ||
|
||
|
||
## Logging errors | ||
|
||
The specification [states](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-6-cache.md#error-handling): | ||
|
||
> While caching is often an important part of application performance, it should never be a critical part of application | ||
> functionality. Thus, an error in a cache system SHOULD NOT result in application failure. | ||
|
||
Once you've got your pool instance, almost all exceptions thrown by the storage will be caught and ignored. The only | ||
storage exceptions that bubble up implement `Psr\Cache\InvalidArgumentException` and are typically caused by invalid | ||
key errors. To be PSR-6 compliant, cache keys must not contain the following characters: `{}()/\@:`. However different | ||
storage adapters may have further restrictions. Check the documentation for your particular adapter to be sure. | ||
|
||
We strongly recommend tracking exceptions caught from storage, either by logging them or recording them in some other | ||
way. Doing so is as simple as adding an [`ExceptionHandler` plugin](zend.cache.storage.plugin.html#3.4). Say you have a | ||
[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) compliant logger | ||
called `$logger`: | ||
|
||
|
||
```php | ||
$cacheLogger = function (\Exception $e) use ($logger) { | ||
$message = sprintf( | ||
'[CACHE] %s:%s %s "%s"', | ||
$exception->getFile(), | ||
$exception->getLine(), | ||
$exception->getCode(), | ||
$exception->getMessage() | ||
); | ||
$logger->error($message); | ||
}; | ||
} | ||
$storage = StorageFactory::factory([ | ||
'adapter' => [ | ||
'name' => 'apc', | ||
], | ||
'plugins' => [ | ||
'exceptionhandler' => [ | ||
'exception_callback' => $cacheLogger, | ||
'throw_exceptions' => true, | ||
], | ||
], | ||
]); | ||
|
||
$pool = new CacheItemPoolAdapter($storage); | ||
``` | ||
|
||
Note that `throw_exceptions` should always be `true` (the default) or you will not get the correct return values from | ||
calls on the pool such as `save()`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because we catch the exceptions and return false from these methods in case of errors, as per the spec. If they're not thrown we'll always return true. I'll clarify the docs. |
||
|
||
|
||
## Supported data types | ||
|
||
As per [the specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-6-cache.md#data), the | ||
following data types can be stored in cache: `string`, `integer`, `float`, `boolean`, `null`, `array`, `object` and be | ||
returned as a value with exactly the same type. | ||
|
||
Not all adapters can natively store all these types. For instance, Redis stores booleans and integers as a string. Where | ||
this is the case *all* values will be automatically run through `serialize()` on save and `unserialize()` on get: you | ||
do not need to use a `Zend\Cache\Storage\Plugin\Serializer` plugin. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ... but you can if you like to change the serializer to igbinary. |
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"title": "Zend\\Cache", | ||
"target": "html/", | ||
"content": [ | ||
"book/zend.cache.storage.adapter.md", | ||
"book/zend.cache.storage.capabilities.md", | ||
"book/zend.cache.storage.plugin.md", | ||
"book/zend.cache.pattern.md", | ||
"book/zend.cache.pattern.callback-cache.md", | ||
"book/zend.cache.pattern.class-cache.md", | ||
"book/zend.cache.pattern.object-cache.md", | ||
"book/zend.cache.pattern.output-cache.md", | ||
"book/zend.cache.pattern.capture-cache.md", | ||
"book/zend.cache.psr.cacheitempooladapter.md" | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?php | ||
/** | ||
* Zend Framework (http://framework.zend.com/). | ||
* | ||
* @link http://github.com/zendframework/zend-cache for the canonical source repository | ||
* @copyright Copyright (c) 2016 Zend Technologies USA Inc. (http://www.zend.com) | ||
* @license http://framework.zend.com/license/new-bsd New BSD License | ||
*/ | ||
|
||
namespace Zend\Cache\Psr; | ||
|
||
use Psr\Cache\CacheException as CacheExceptionInterface; | ||
|
||
class CacheException extends \RuntimeException implements CacheExceptionInterface | ||
{ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
<?php | ||
/** | ||
* Zend Framework (http://framework.zend.com/). | ||
* | ||
* @link http://github.com/zendframework/zend-cache for the canonical source repository | ||
* @copyright Copyright (c) 2016 Zend Technologies USA Inc. (http://www.zend.com) | ||
* @license http://framework.zend.com/license/new-bsd New BSD License | ||
*/ | ||
|
||
namespace Zend\Cache\Psr; | ||
|
||
use DateInterval; | ||
use DateTime; | ||
use DateTimeInterface; | ||
use DateTimeZone; | ||
use Psr\Cache\CacheItemInterface; | ||
|
||
final class CacheItem implements CacheItemInterface | ||
{ | ||
/** | ||
* Cache key | ||
* @var string | ||
*/ | ||
private $key; | ||
|
||
/** | ||
* Cache value | ||
* @var mixed|null | ||
*/ | ||
private $value; | ||
|
||
/** | ||
* True if the cache item lookup resulted in a cache hit or if they item is deferred or successfully saved | ||
* @var bool | ||
*/ | ||
private $isHit = false; | ||
|
||
/** | ||
* Timestamp item will expire at if expiresAt() called, null otherwise | ||
* @var int|null | ||
*/ | ||
private $expiration = null; | ||
|
||
/** | ||
* Seconds after item is stored it will expire at if expiresAfter() called, null otherwise | ||
* @var int|null | ||
*/ | ||
private $ttl = null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please can you add code comments for each of the properties - thanks There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will do. |
||
|
||
/** | ||
* @var DateTimeZone | ||
*/ | ||
private $tz; | ||
|
||
/** | ||
* Constructor. | ||
* | ||
* @param string $key | ||
* @param mixed $value | ||
* @param bool $isHit | ||
*/ | ||
public function __construct($key, $value, $isHit) | ||
{ | ||
$this->key = $key; | ||
$this->value = $isHit ? $value : null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. null is a valid value for PSR-6 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, which is why you need to check isHit() - afaik the value should be null if its not a hit. |
||
$this->isHit = $isHit; | ||
$this->tz = new DateTimeZone('UTC'); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getKey() | ||
{ | ||
return $this->key; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function get() | ||
{ | ||
return $this->value; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need to check if isHit before you return anything There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The value is set to |
||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function isHit() | ||
{ | ||
if (! $this->isHit) { | ||
return false; | ||
} | ||
$ttl = $this->getTtl(); | ||
return $ttl === null || $ttl > 0; | ||
} | ||
|
||
/** | ||
* Sets isHit value | ||
* | ||
* This function is called by CacheItemPoolAdapter::saveDeferred() and is not intended for use by other calling | ||
* code. | ||
* | ||
* @param boolean $isHit | ||
* @return $this | ||
*/ | ||
public function setIsHit($isHit) | ||
{ | ||
$this->isHit = $isHit; | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function set($value) | ||
{ | ||
$this->value = $value; | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function expiresAt($expiration) | ||
{ | ||
if (! ($expiration === null || $expiration instanceof DateTimeInterface)) { | ||
throw new InvalidArgumentException('$expiration must be null or an instance of DateTimeInterface'); | ||
} | ||
|
||
$this->expiration = $expiration instanceof DateTimeInterface ? $expiration->getTimestamp() : null; | ||
$this->ttl = null; | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function expiresAfter($time) | ||
{ | ||
if ($time instanceof DateInterval) { | ||
$end = new DateTime('now', $this->tz); | ||
$end->add($time); | ||
$this->ttl = $end->getTimestamp() - time(); | ||
} elseif (is_int($time) || $time === null) { | ||
$this->ttl = $time; | ||
} else { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm missing the handling of |
||
throw new InvalidArgumentException(sprintf('Invalid $time "%s"', gettype($time))); | ||
} | ||
|
||
$this->expiration = null; | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* Returns number of seconds until item expires | ||
* | ||
* If NULL, the pool should use the default TTL for the storage adapter. If <= 0, the item has expired. | ||
* | ||
* @return int|null | ||
*/ | ||
public function getTtl() | ||
{ | ||
$ttl = $this->ttl; | ||
if ($this->expiration !== null) { | ||
$ttl = $this->expiration - time(); | ||
} | ||
return $ttl; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is confusing because:
apc.use_request_time
which is enabled be default.EDIT: Uuups you mentioned the APC behavior already in the next paragraph