Skip to content

Instantly share code, notes, and snippets.

@mindplay-dk
Last active May 6, 2017 18:20
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mindplay-dk/5152d52eb4f19d7c66c1 to your computer and use it in GitHub Desktop.
Save mindplay-dk/5152d52eb4f19d7c66c1 to your computer and use it in GitHub Desktop.
Simplied cache interface
<?php
namespace Psr\Cache;
/**
* Cache defines a common driver interface for interacting with a cache back-end.
*/
interface Driver
{
/**
* Read a single value from the cache.
*
* If distinguishing a cachced FALSE value vs FALSE on cache miss, the optional
* second argument can be used to obtain a value indicating success.
*
* @param string $key The key of the cache value to fetch.
* @param bool &$found Optional var reference, set to TRUE on success, or FALSE on failure
*
* @return mixed The cached data, or FALSE on cache miss
*/
public function read($key, &$found=null);
/**
* Write a single value to the cache.
*
* @param string $key The cache key.
* @param mixed $value The cache value.
* @param int|null $expiration The cache item expiration in seconds (or NULL for infinity)
*
* @return boolean TRUE if the value was successfully stored in the cache, FALSE otherwise.
*/
public function write($key, $value, $expiration = null);
/**
* Renew a cached value, resetting it's expiration time, if present.
*
* @param string $key The cache key.
* @param int|null $expiration The cache item expiration in seconds (or NULL for infinity)
*
* @return boolean TRUE if the value was successfully renewed in the cache, FALSE otherwise.
*/
public function renew($key, $expiration);
/**
* Check if a value was previously {link set()} during the lifetime of this object, or
* subsequent to the last call to {link clear()}
*
* @param string $key The cache key
*
* @return boolean TRUE if a value with the given key was previous set()
*/
public function refreshed($key);
/**
* Read multiple values from the cache.
*
* The return array maps each given key to a value - keys that were not found
* will not be present in the array. If none of the given keys were found, the
* returned array will be empty.
*
* @param string[] list of keys to be read from the cache.
*
* @return array $keys a map where each given key maps to the found cached value.
*/
public function readAll(array $keys);
/**
* Tests if a value exists in the cache.
*
* @param string $key The cache key of the value to check for.
*
* @return boolean TRUE if a cached value exists for the given cache key, FALSE otherwise.
*/
public function exists($key);
/**
* Removes a value from the cache.
*
* @param string $key The cache key.
*
* @return boolean TRUE, if the cached value was successfully removed; otherwise FALSE.
*/
public function remove($key);
/**
* Removes a list of values from the cache.
*
* @param string[] $keys list of keys to be removed
*
* @return bool TRUE, if all of the cached values were successfully removed; otherwise FALSE
*/
public function removeAll(array $keys);
/**
* Clears the cache.
*
* @return boolean TRUE, if the cache was successfully cleared; FALSE, if there was an error.
*/
public function clear();
}
@mindplay-dk
Copy link
Author

@mindplay-dk
Copy link
Author

References:

@mindplay-dk
Copy link
Author

Clarifications:

I chose "read" and "write", rather than "get" and "set", because getting and setting happens at the Pool/Item level, and doesn't imply something being physically read and written.

Deferred writes, and possibly delete operations (something the current proposal doesn't support) belongs at the Pool/Item level, and doesn't appear in the proposed Driver interface, as deferring operations is not an interaction with the cache backend and therefore doesn't need to be a concern of the Driver.

Regarding the problem of distinguishing a cached FALSE value vs FALSE on cache miss, this interface provides 3 ways of addressing that: an optional second out argument on the read() method (as per apc_fetch), using isset() to check for key presence in the return value from the readAll() method, or doing a subsequent call to exists() after getting a FALSE from read(); the latter may perform less than optimally depending on the Driver implementation.

Options for expiration values (fixed date/time vs timestamp vs seconds) were deliberately left out - this convenience can easily be implemented in a layer consuming Drivers, and doesn't need to complicate individual Driver implementations.

For expiration, I chose duration in seconds, rather than an absolute timestamp, because cache servers are sometimes remote, and the local and remote clock may not be perfectly in sync. Most referenced sources specify expiration as a duration, and again, a layer can easily calculate expiration in seconds based on local clock and an absolute timestamp, so it doesn't need to exist at this level of abstraction.

A refreshed() method was added to help eliminate race conditions by allowing a consumer to check if a value with a given key was written during the lifetime of the Driver instance (or subsequent to the last call to clear()) - as well as to support the isRegenerating() method defined by Items at the Pool/Item level.

A renew() method was added, as per ZF2, to allow a possible optimization permitting some backends to reset the expiration without reading the value.

The touch() method as per ZF2 was deliberately left out, as this can easily be implemented in a layer, as can the default TTL setting which gets applied by that function - the setting itself isn't required by any cache backends and doesn't belong at the Driver level.

Also note the absence of a prefix (or "namespace") setting for keys, such as found in Yii and Doctrine - again, this can be implemented in a layer, if needed, and does not need to be an individual Driver concern. (I'm not 100% certain about this)

A catch-all "options" setting, such as found in ZF2 and Stash drivers, is also absent - options specific to a Driver are a concern of that Driver and not a general concern.

General-purpose "metadata" for items, as per ZF2, were also left out, again, because any such metadata is going to be provider-specific and belongs in Driver implementations, in rare cases where a provider may have such a feature. (not 100% certain about this either.)

Stash drivers support an isAvailable() method - this was left out, as it is a static method which can't be called in the abstract - in practice, your code would read if (DriverX::isAvailable()) which is coupled directly to the XCache class regardless of the interface, so it doesn't provide any abstraction; static methods in interfaces are sometimes seen as a point of contention in PHP. I'm not against it, but don't feel it is necessary - just mentioning it here for completeness as far as referencing the named sources.

Stash drivers also provide a purge() method, which could be useful for some implementations like file-cache, but may not be strictly necessary in the large majority of use-cases. I left it out for the time being, but I'm not against that either. A better method name might be "expire", as "purge" is somewhat synonymous and could be confused with "clear". Again, not in the original proposal and mentioned here for completeness.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment