Another approach (which I haven't tried, but sounds very interesting) is to take advantage of the opcache as a key value cache. This graphiq post contains more details but no real code unfortunately (and note the comments from Kerry Schwab).
The gist of it is to ensure that the opcache is enabled and has enough memory allocated for the data that you need to cache and then something along the lines of the following (lifted from the article, check it out in full). Cache expiration (aside from manual deletion) would need to be handled too, but wouldn't be hard to add (e.g. wrap your data in a containing object with an expiry time and check it in your cache_get
, deleting and ignoring the record if it's expired).
function cache_set($key, $val) {
$val = var_export($val, true);
// HHVM fails at __set_state, so just use object cast for now
$val = str_replace('stdClass::__set_state', '(object)', $val);
// Write to temp file first to ensure atomicity
$tmp = "/tmp/$key." . uniqid('', true) . '.tmp';
file_put_contents($tmp, '<?php $val = ' . $val . ';', LOCK_EX);
rename($tmp, "/tmp/$key");
}
function cache_get($key) {
@include "/tmp/$key";
return isset($val) ? $val : false;
}
Because of the opcache this functions as an in memory cache, but it avoids the overhead of serialization and deserialization. I guess that the cache_set should also call opcache_invalidate
when writing (and in the cache_delete
function which doesn't exist in their examples) but otherwise it seems sound for a cache which doesn't need to be shared between servers.
Edit: An example implementation with cache expiry (only accurate to a second, could use microtime(true)
if more accuracy is required) below. Minimal testing actually done, and I dropped the HHVM specific replacement, so YMMV. Suggestions for improvements welcome.
class Cache {
private $root;
private $compile;
private $ttl;
public function __construct($options = []) {
$this->options = array_merge(
array(
'root' => sys_get_temp_dir(),
'ttl' => false,
),
$options
);
$this->root = $this->options['root'];
$this->ttl = $this->options['ttl'];
}
public function set($key, $val, $ttl = null) {
$ttl = $ttl === null ? $this->ttl : $ttl;
$file = md5($key);
$val = var_export(array(
'expiry' => $ttl ? time() + $ttl : false,
'data' => $val,
), true);
// Write to temp file first to ensure atomicity
$tmp = $this->root . '/' . $file . '.' . uniqid('', true) . '.tmp';
file_put_contents($tmp, '<?php $val = ' . $val . ';', LOCK_EX);
$dest = $this->root . '/' . $file;
rename($tmp, $dest);
opcache_invalidate($dest);
}
public function get($key) {
@include $this->root . '/' . md5($key);
// Not found
if (!isset($val)) return null;
// Found and not expired
if (!$val['expiry'] || $val['expiry'] > time()) return $val['data'];
// Expired, clean up
$this->remove($key);
}
public function remove($key) {
$dest = $this->root . '/' . md5($key);
if (@unlink($dest)) {
// Invalidate cache if successfully written
opcache_invalidate($dest);
}
}
}