zendframework/zf2 — pull request #5386 by Thinkscape (2013-10-30 18:00:49+00:00)
Title: [Cache] Add adhoc TTL support, fix per-item ttl handling.

### Why can't I specify TTL ?
Since I remember working with code-level cache solutions there was always an easy method to specify TTL on per-item basis. It is a [common](http://us3.php.net/apc_store) [feature](http://us3.php.net/manual/en/function.wincache-ucache-add.php) [among](http://www.php.net/manual/en/memcache.add.php) most well-known libraries and extensions and was present in [ZF1 Zend_Cache](https://github.com/zendframework/zf1/blob/master/library/Zend/Cache/Backend/File.php#L243). Sadly, zf2 is missing this.

````php
// APC
apc_store ( string $key , mixed $var [, int $ttl = 0 ] );

// Memcache
Memcache::add( string $key , mixed $var [, int $flag [, int $expire ]] );

// Wincache
wincache_ucache_add ( string $key , mixed $value [, int $ttl = 0 ] );

// Zend Server
zend_shm_cache_store($internalKey, $value, $ttl);

// Zend_Cache
Zend_Cache_Backend_File::save($data, $id, $tags = array(), $specificLifetime = false);

// etc.
````

### Problem 1
ZF2 Cache Storage does not allow to store items with specific TTL.

The first problem was missing `$ttl` parameter in all [Cache\StorageInterface](https://github.com/zendframework/zf2/blob/master/library/Zend/Cache/Storage/StorageInterface.php#L99) write methods (i.e. `setItem()`, `replaceItem()` etc.). Without this, the only way to set an item with a specific TTL was:

````php
/* @var $cache StorageInterface */
$oldTtl = $cache->getOptions()->getTtl();
$cache->getOptions()->setTtl(60); // set to 1 minute
$cache->setItem('foo','bar'); // store the item
$cache->getOptions()->setTtl($oldTtl); // restore previous ttl
// *phew*
````

### Problem 2

Unfortunately, the above code does not work properly. This is because many ZF2 adapters do not support or understand item-level TTL, unless it is handled internally by the backend (i.e. `apc_store()` natively supports TTL and it's getting fed with the one from options).

For example, `Storage\Memory` and `Storage\Filesystem` would exhibit unexpected behavior when discarding old items or fetching items if we tried to manipulate ttl:

````php
/* @var $cache StorageInterface */
$cache = new \Zend\Cache\Storage\Adapter\Memory();
$cache->getOptions()->setTtl(3600); // set TTL to 1 hour
$cache->setItem('foo',1); // store an item

$cache->getOptions()->setTtl(60); // set TTL to 1 minute
$cache->setItem('bar',1); // store another item

sleep(61);

var_dump($cache->hasItem('bar')); // false
var_dump($cache->hasItem('foo')); // false
// *poof* "foo" item disappeared but should live for an hour
````

### The goal
````php
/* @var $cache StorageInterface */
$cache->setItem('foo','1', 60); // store the item for 60s
$cache->setItem('bar','2', 3600); // store the item for 1 hour

// store the item with default TTL setting from the storage
$cache->setItem('bar','2');

// replace multiple items and set their TTL to 60s
$cache->replaceItems(['foo'=>10,'bar'=>20], 60);

// increment item by 5 and make it live for 1h
$cache->incrementItem('foo', 5, 3600);
````

### Solution

- [x] Add `$ttl` support to StorageInterface.
- [x] Refactor `Cache\Storage\Adapter\AbstractAdapter` to handle $ttl in write operations.
- [x] Pass on `$ttl` to listeners (plugins).
- [x] Refactor all `Adapter\*` classes to use per-item `$ttl` and pass it on to underlying backend.
- [x] Refactor `Adapter\Memory` to support per-item TTL.
- [x] Refactor `Adapter\Filesystem` to support per-item TTL.
- [x] Add TTL tests that work with ad-hoc ttl setting.
- [ ] Add overlapping temporal per-item ttl tests.