A. Caching systems in general:
- there are many caching systems out there, with totally different APIs, levels of complexity, implementations and use-cases, so a minimum common API should provide some level of portability.
- caching systems can work synchronously or asynchronously
- usually caching systems store items for a limited amount of time (expiration, time-to-live, etc.)
- some caching systems expose the expiration information in their API, others don't
- those caching systems who use expiration may chose to use a predefined value, a system imposed value, or let the API consumer specify a value
- the most basic operations are the storing and retrieval of things from the cache
- caching systems don't always guarantee that an item is or will be stored when asked to
- some caching systems function as a LRU (last recently used) system - which means that data that is not used often is evicted at some point
- in fact, caching systems don't guarantee anything - they do their best to store a piece of information for a high speed delivery when needed
- high speed delivery usually means that other aspects of data storage (integrity, availability) are sacrificed
B. Given these, the lowest possible level of interaction with a caching system would consist in just two methods:
<?php
namespace Psr\Cache;
interface Store
{
/**
* Retrievs a named value from cache. Must return null if
* the value is expired or missing completely.
*
* @param string $key The name of the stored value to be retrieved.
* @return mixed|null The stored value if found and valid, null otherwise.
*/
public function retrieve($key);
/**
* Attempts to store a named value into cache.
*
* @param string $key The name of the value to be stored.
* @param mixed $value The actual value to be stored. If null, any existing
* values for the same key will be removed or scheduled
* for removal ($ttl will be ignored in this case).
* @param number $ttl The amount of time we need the value stored. Ignored
* by systems that don't support TTL. A $ttl of value 0
* means no expiration. A value of -1 deletes the item.
* @return bool A success status. For asynchronous caches, will always
* return true if the value was successfully queued
* internally for storage.
*/
public function store($key, $value = null, $ttl = 0);
}
Note: we're assuming PHP 5.3.3+ (a method having the same name as a namespaced class will not be treated as constructors). For lower PHP versions, a method or interface rename would do the trick.
These two methods are the basic requirements for using a caching system.
A basic reference implementation (using the APC engine) could potentially be done like this:
<?php
class ApcStoreRi implements \Psr\Cache\Store
{
/**
* {@inheritdoc}
*/
public function retrieve($key)
{
$success = false;
$value = apc_fetch($key, $success);
return $success ? $value : null;
}
/**
* {@inheritdoc}
*/
public function store($key, $value = null, $ttl = 0)
{
return ($ttl < 0) ? apc_delete($key) : apc_store($key, $value, $ttl);
}
}
Note: The ApcStoreRi is just a potential implementation/example, and should be treated as such.
At times, special needs may arise:
- Manual invalidation/eviction:
A convenience delete($key) method could be added for such an operation (when the caching system explicitly exposes the invalidation functionality), but should not be required, so let's add it in a separate interface:
<?php
namespace Psr\Cache;
interface EvictableStore extends Store
{
/**
* Attempts to remove a previously stored value.
*
* @param string $key The name of the stored value to be evicted.
* @return bool A success status. For asynchronous caches,
* will always return true if the value was
* successfully queued internally for eviction.
*/
public function evict($key);
}
A convenience abstraction could also be added:
<?php
abstract class EvictableStoreRi implements \Psr\Cache\EvictableStore
{
/**
* {@inheritdoc}
*/
public function evict($key)
{
return $this->store($key, null, -1);
}
}
Our APC RI could potentially be done like this:
<?php
class ApcStoreRi extends Psr\Cache\EvictableStoreRi
{
// ... insert the store() and retrieve() methods from the
// previous APC RI class here
}
... or like this (if we keep the previous ApcStoreRi class):
<?php
class ApcEvictableStoreRi extends ApcStoreRi implements \Psr\Cache\EvictableStore
{
/**
* {@inheritdoc}
*/
public function evict($key)
{
return apc_delete($key);
}
}
Note: Normally, we would also implement the EvictableStore in the previously described ApcStoreRi directly. The modified ApcStoreRi and the ApcEvistableStoreRi are just potential implementations/examples, and should be treated as such.
- Checking for an item's existance.
A convenience contains($key) method could be added for such an operation, but should not be required, so let's add it in a separate interface:
<?php
namespace Psr\Cache;
interface QueryableStore extends Store
{
/**
* Attempts to check if a value exists in the cache for a given name.
*
* @param string $key The name of the stored value to be verified.
* @return bool Weather the value exists in the cache or not.
*/
public function contains($key);
}
A convenience abstraction could also be added:
<?php
abstract class QueryableStoreRi implements QueryableStore
{
/**
* {@inheritdoc}
*/
public function contains($key)
{
return return (null !== $this->retrieve($key));
}
}
Our APC RI could potentially be done like this:
<?php
class ApcStoreRi extends Psr\Cache\QueryableStoreRi
{
// ... insert the store() and retrieve() methods from
// the previous APC RI class here
}
... or like this (if we keep the previous ApcStoreRi class):
<?php
class ApcQueryableStoreRi extends ApcStoreRi implements Psr\Cache\QueryableStore
{
/**
* {@inheritdoc}
*/
public function contains($key)
{
return (bool) apc_exists($key);
}
}
- Bulk core (store and retrieve) operations:
Sometimes, bulk operations are desirable, so let's define an interface for multi key API calls:
<?php
namespace Psr\Cache;
interface MultiAccessStore extends Store
{
/**
* Retrievs multiple named values from cache.
*
* @param array|Traversable $keys A list of value names to retrieve.
* @return array A key/value map of stored value.
*/
public function multiRetrieve($keys);
/**
* Attempts to store a named value into the cache.
*
* @param array|Traversable $values A key/values map of values to be stored.
* @return array A key/success map.
*/
public function multiStore($values);
}
A convenience abstraction could also be added:
<?php
abstract class MultiAccessStoreRi implements MultiAccessStore
{
/**
* {@inheritdoc}
*/
public function multiRetrieve($keys)
{
$results = array();
if (is_array($keys) || ($keys instanceof \Traversable)) {
foreach ($keys as $key) {
$results[$key] = $this->retrieve($key);
}
}
return $results;
}
/**
* {@inheritdoc}
*/
public function multiStore($values)
{
$results = array();
if (is_array($values) || ($values instanceof \Traversable)) {
foreach ($values as $key => $value) {
if (is_array($value)) {
$params = array_merge(array($key), $value);
}
else {
$params = array($key, $value);
}
$callback = array($this, 'store');
$results[$key] = call_user_func_array($callback, $params);
}
}
return $results;
}
}
Our APC RI could potentially be done like this:
<?php
class ApcStoreRi extends Psr\Cache\MultiAccessStoreRi
{
// ... insert the store() and retrieve() methods from the
// previous APC RI class here
}
... or like this (if we keep the previous ApcStoreRi class):
<?php
class ApcQueryableStoreRi extends ApcStoreRi implements Psr\Cache\MultiAccessStoreRi
{
/**
* {@inheritdoc}
*/
public function multiRetrieve($keys)
{
return $this->retrieve($keys);
}
/**
* {@inheritdoc}
* Please note that TTL is not supported in this implementation.
*/
public function multiStore($values)
{
return apc_store($values);
}
}
C. Conclusion.
While use-cases other than the above may arise, they could be added to the specification using the same mechanism as above.
Our final specification could look like this: