Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ php:
- 5.4
- 5.5
- 5.6
- 7
- 7.0
- 7.1
- 7.2
- 7.3
# - hhvm # requires legacy phpunit & ignore errors, see below

# lock distro so new future defaults will not break the build
Expand All @@ -24,6 +27,6 @@ sudo: false

install:
- composer install --no-interaction

script:
- ./vendor/bin/phpunit --coverage-text
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ $cache->set('foo', 'bar', 60);
This example eventually sets the value of the key `foo` to `bar`. If it
already exists, it is overridden.

This interface suggests that cache implementations SHOULD use a monotonic
time source if available. Given that a monotonic time source is only
available as of PHP 7.3 by default, cache implementations MAY fall back
to using wall-clock time.
While this does not affect many common use cases, this is an important
distinction for programs that rely on a high time precision or on systems
that are subject to discontinuous time adjustments (time jumps).
This means that if you store a cache item with a TTL of 30s and then
adjust your system time forward by 20s, the cache item SHOULD still
expire in 30s.

#### delete()

The `delete(string $key): PromiseInterface<bool>` method can be used to
Expand Down Expand Up @@ -230,6 +241,16 @@ $cache->set('bar', '2');
$cache->set('baz', '3');
```

This cache implementation is known to rely on wall-clock time to schedule
future cache expiration times when using any version before PHP 7.3,
because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
While this does not affect many common use cases, this is an important
distinction for programs that rely on a high time precision or on systems
that are subject to discontinuous time adjustments (time jumps).
This means that if you store a cache item with a TTL of 30s on PHP < 7.3
and then adjust your system time forward by 20s, the cache item may
expire in 10s. See also [`set()`](#set) for more details.

## Common usage

### Fallback get
Expand Down
30 changes: 26 additions & 4 deletions src/ArrayCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class ArrayCache implements CacheInterface
private $limit;
private $data = array();
private $expires = array();
private $supportsHighResolution;

/**
* The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
Expand All @@ -36,17 +37,30 @@ class ArrayCache implements CacheInterface
* $cache->set('baz', '3');
* ```
*
* This cache implementation is known to rely on wall-clock time to schedule
* future cache expiration times when using any version before PHP 7.3,
* because a monotonic time source is only available as of PHP 7.3 (`hrtime()`).
* While this does not affect many common use cases, this is an important
* distinction for programs that rely on a high time precision or on systems
* that are subject to discontinuous time adjustments (time jumps).
* This means that if you store a cache item with a TTL of 30s on PHP < 7.3
* and then adjust your system time forward by 20s, the cache item may
* expire in 10s. See also [`set()`](#set) for more details.
*
* @param int|null $limit maximum number of entries to store in the LRU cache
*/
public function __construct($limit = null)
{
$this->limit = $limit;

// prefer high-resolution timer, available as of PHP 7.3+
$this->supportsHighResolution = \function_exists('hrtime');
}

public function get($key, $default = null)
{
// delete key if it is already expired => below will detect this as a cache miss
if (isset($this->expires[$key]) && $this->expires[$key] < \microtime(true)) {
if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
unset($this->data[$key], $this->expires[$key]);
}

Expand All @@ -71,7 +85,7 @@ public function set($key, $value, $ttl = null)
// sort expiration times if TTL is given (first will expire first)
unset($this->expires[$key]);
if ($ttl !== null) {
$this->expires[$key] = \microtime(true) + $ttl;
$this->expires[$key] = $this->now() + $ttl;
\asort($this->expires);
}

Expand All @@ -84,7 +98,7 @@ public function set($key, $value, $ttl = null)

// check to see if the first in the list of expiring keys is already expired
// if the first key is not expired, we have to overwrite by using LRU info
if ($key === null || $this->expires[$key] > \microtime(true)) {
if ($key === null || $this->now() - $this->expires[$key] < 0) {
\reset($this->data);
$key = \key($this->data);
}
Expand Down Expand Up @@ -141,7 +155,7 @@ public function clear()
public function has($key)
{
// delete key if it is already expired
if (isset($this->expires[$key]) && $this->expires[$key] < \microtime(true)) {
if (isset($this->expires[$key]) && $this->now() - $this->expires[$key] > 0) {
unset($this->data[$key], $this->expires[$key]);
}

Expand All @@ -156,4 +170,12 @@ public function has($key)

return Promise\resolve(true);
}

/**
* @return float
*/
private function now()
{
return $this->supportsHighResolution ? \hrtime(true) * 1e-9 : \microtime(true);
}
}
11 changes: 11 additions & 0 deletions src/CacheInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ public function get($key, $default = null);
* This example eventually sets the value of the key `foo` to `bar`. If it
* already exists, it is overridden.
*
* This interface suggests that cache implementations SHOULD use a monotonic
* time source if available. Given that a monotonic time source is only
* available as of PHP 7.3 by default, cache implementations MAY fall back
* to using wall-clock time.
* While this does not affect many common use cases, this is an important
* distinction for programs that rely on a high time precision or on systems
* that are subject to discontinuous time adjustments (time jumps).
* This means that if you store a cache item with a TTL of 30s and then
* adjust your system time forward by 20s, the cache item SHOULD still
* expire in 30s.
*
* @param string $key
* @param mixed $value
* @param ?float $ttl
Expand Down