Created
September 2, 2014 13:48
-
-
Save kazuhei/063960ff9d4e11183735 to your computer and use it in GitHub Desktop.
fuelphp Cache_Storage_Memcachier to use memcachier on heroku
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Part of the Fuel framework. | |
* | |
* @package Fuel | |
* @version 1.7 | |
* @author Fuel Development Team | |
* @license MIT License | |
* @copyright 2010 - 2014 Fuel Development Team | |
* @link http://fuelphp.com | |
*/ | |
namespace Fuel\Core; | |
class Cache_Storage_Memcachier extends \Cache_Storage_Driver | |
{ | |
/** | |
* @const string Tag used for opening & closing cache properties | |
*/ | |
const PROPS_TAG = 'Fuel_Cache_Properties'; | |
/** | |
* @var array driver specific configuration | |
*/ | |
protected $config = array(); | |
/* | |
* @var Memcached storage for the memcached object | |
*/ | |
protected static $memcached = false; | |
// --------------------------------------------------------------------- | |
public function __construct($identifier, $config) | |
{ | |
parent::__construct($identifier, $config); | |
$this->config = isset($config['memcachier']) ? $config['memcachier'] : array(); | |
// make sure we have a memcache id | |
$this->config['cache_id'] = $this->_validate_config('cache_id', isset($this->config['cache_id']) | |
? $this->config['cache_id'] : 'fuel'); | |
// check for an expiration override | |
$this->expiration = $this->_validate_config('expiration', isset($this->config['expiration']) | |
? $this->config['expiration'] : $this->expiration); | |
if (static::$memcached === false) | |
{ | |
// make sure we have memcached servers configured | |
$this->config['servers'] = $this->_validate_config('servers', $this->config['servers']); | |
// do we have the PHP memcached extension available | |
if ( ! class_exists('Memcached') ) | |
{ | |
throw new \FuelException('Memcached cache are configured, but your PHP installation doesn\'t have the Memcached extension loaded.'); | |
} | |
// instantiate the memcached object | |
static::$memcached = new \Memcached(); | |
// add auth condition | |
static::$memcached->setSaslAuthData( getenv("MEMCACHIER_USERNAME"), getenv("MEMCACHIER_PASSWORD") ); | |
// add the configured servers | |
static::$memcached->addServers($this->config['servers']); | |
// check if we can connect to the server(s) | |
if (static::$memcached->getVersion() === false) | |
{ | |
throw new \FuelException('Memcached cache are configured, but there is no connection possible. Check your configuration.'); | |
} | |
} | |
} | |
// --------------------------------------------------------------------- | |
/** | |
* Check if other caches or files have been changed since cache creation | |
* | |
* @param array | |
* @return bool | |
*/ | |
public function check_dependencies(array $dependencies) | |
{ | |
foreach($dependencies as $dep) | |
{ | |
// get the section name and identifier | |
$sections = explode('.', $dep); | |
if (count($sections) > 1) | |
{ | |
$identifier = array_pop($sections); | |
$sections = '.'.implode('.', $sections); | |
} | |
else | |
{ | |
$identifier = $dep; | |
$sections = ''; | |
} | |
// get the cache index | |
$index = static::$memcached->get($this->config['cache_id'].$sections); | |
// get the key from the index | |
$key = isset($index[$identifier][0]) ? $index[$identifier] : false; | |
// key found and newer? | |
if ($key === false or $key[1] > $this->created) | |
{ | |
return false; | |
} | |
} | |
return true; | |
} | |
/** | |
* Delete Cache | |
*/ | |
public function delete() | |
{ | |
// get the memcached key for the cache identifier | |
$key = $this->_get_key(true); | |
// delete the key from the memcached server | |
if ($key and static::$memcached->delete($key) === false) | |
{ | |
if (static::$memcached->getResultCode() !== \Memcached::RES_NOTFOUND) | |
{ | |
throw new \FuelException('Memcached returned error code "'.static::$memcached->getResultCode().'" on delete. Check your configuration.'); | |
} | |
} | |
$this->reset(); | |
} | |
/** | |
* Purge all caches | |
* | |
* @param limit purge to subsection | |
* @return bool | |
*/ | |
public function delete_all($section) | |
{ | |
// determine the section index name | |
$section = $this->config['cache_id'].(empty($section)?'':'.'.$section); | |
// get the directory index | |
$index = static::$memcached->get($this->config['cache_id'].'__DIR__'); | |
if (is_array($index)) | |
{ | |
// limit the delete if we have a valid section | |
if ( ! empty($section)) | |
{ | |
$dirs = in_array($section, $index) ? array($section) : array(); | |
} | |
else | |
{ | |
$dirs = $index; | |
} | |
// loop through the indexes, delete all stored keys, then delete the indexes | |
foreach ($dirs as $dir) | |
{ | |
$list = static::$memcached->get($dir); | |
foreach ($list as $item) | |
{ | |
static::$memcached->delete($item[0]); | |
} | |
static::$memcached->delete($dir); | |
} | |
// update the directory index | |
$index = array_diff($index, $dirs); | |
static::$memcached->set($this->config['cache_id'].'__DIR__', $index); | |
} | |
} | |
// --------------------------------------------------------------------- | |
/** | |
* Prepend the cache properties | |
* | |
* @return string | |
*/ | |
protected function prep_contents() | |
{ | |
$properties = array( | |
'created' => $this->created, | |
'expiration' => $this->expiration, | |
'dependencies' => $this->dependencies, | |
'content_handler' => $this->content_handler | |
); | |
$properties = '{{'.static::PROPS_TAG.'}}'.json_encode($properties).'{{/'.static::PROPS_TAG.'}}'; | |
return $properties.$this->contents; | |
} | |
/** | |
* Remove the prepended cache properties and save them in class properties | |
* | |
* @param string | |
* @throws UnexpectedValueException | |
*/ | |
protected function unprep_contents($payload) | |
{ | |
$properties_end = strpos($payload, '{{/'.static::PROPS_TAG.'}}'); | |
if ($properties_end === FALSE) | |
{ | |
throw new \UnexpectedValueException('Cache has bad formatting'); | |
} | |
$this->contents = substr($payload, $properties_end + strlen('{{/'.static::PROPS_TAG.'}}')); | |
$props = substr(substr($payload, 0, $properties_end), strlen('{{'.static::PROPS_TAG.'}}')); | |
$props = json_decode($props, true); | |
if ($props === NULL) | |
{ | |
throw new \UnexpectedValueException('Cache properties retrieval failed'); | |
} | |
$this->created = $props['created']; | |
$this->expiration = is_null($props['expiration']) ? null : (int) ($props['expiration'] - time()); | |
$this->dependencies = $props['dependencies']; | |
$this->content_handler = $props['content_handler']; | |
} | |
/** | |
* Save a cache, this does the generic pre-processing | |
* | |
* @return bool success | |
*/ | |
protected function _set() | |
{ | |
// get the memcached key for the cache identifier | |
$key = $this->_get_key(); | |
$payload = $this->prep_contents(); | |
// calculate relative expiration time (eg. 60s) | |
$expiration = !is_null($this->expiration) ? $this->expiration - time() : 0; | |
// if expiration value is less than 30 days, use relative value, otherwise use unix timestamp: | |
$expiration = $expiration <= 2592000 ? (int) $expiration : (int) $this->expiration; | |
// write it to the memcached server | |
if (static::$memcached->set($key, $payload, $expiration) === false) | |
{ | |
throw new \FuelException('Memcached returned error code "'.static::$memcached->getResultCode().'" on write. Check your configuration.'); | |
} | |
// update the index | |
$this->_update_index($key); | |
return true; | |
} | |
/** | |
* Load a cache, this does the generic post-processing | |
* | |
* @return bool success | |
*/ | |
protected function _get() | |
{ | |
// get the memcached key for the cache identifier | |
$key = $this->_get_key(); | |
// fetch the cached data from the Memcached server | |
$payload = static::$memcached->get($key); | |
try | |
{ | |
$this->unprep_contents($payload); | |
} | |
catch (\UnexpectedValueException $e) | |
{ | |
return false; | |
} | |
return true; | |
} | |
/** | |
* validate a driver config value | |
* | |
* @param string name of the config variable to validate | |
* @param mixed value | |
* @return mixed | |
*/ | |
protected function _validate_config($name, $value) | |
{ | |
switch ($name) | |
{ | |
case 'cache_id': | |
if (empty($value) or ! is_string($value)) | |
{ | |
$value = 'fuel'; | |
} | |
break; | |
case 'expiration': | |
if (empty($value) or ! is_numeric($value)) | |
{ | |
$value = null; | |
} | |
break; | |
case 'servers': | |
// do we have a servers config | |
if ( empty($value) OR ! is_array($value)) | |
{ | |
$value = array('default' => array('host' => '127.0.0.1', 'port' => '11211')); | |
} | |
// validate the servers | |
foreach ($value as $key => $server) | |
{ | |
// do we have a host? | |
if ( ! isset($server['host']) OR ! is_string($server['host'])) | |
{ | |
throw new \FuelException('Invalid Memcached server definition in the cache configuration.'); | |
} | |
// do we have a port number? | |
if ( ! isset($server['port']) OR ! is_numeric($server['port']) OR $server['port'] < 1025 OR $server['port'] > 65535) | |
{ | |
throw new \FuelException('Invalid Memcached server definition in the cache configuration.'); | |
} | |
// do we have a relative server weight? | |
if ( ! isset($server['weight']) OR ! is_numeric($server['weight']) OR $server['weight'] < 0) | |
{ | |
// set a default | |
$value[$key]['weight'] = 0; | |
} | |
} | |
break; | |
default: | |
break; | |
} | |
return $value; | |
} | |
/** | |
* Get's the memcached key belonging to the cache identifier | |
* | |
* @param bool if true, remove the key retrieved from the index | |
* @return string | |
*/ | |
protected function _get_key($remove = false) | |
{ | |
// get the current index information | |
list($identifier, $sections, $index) = $this->_get_index(); | |
// get the key from the index | |
$key = isset($index[$identifier][0]) ? $index[$identifier][0] : false; | |
if ($remove === true) | |
{ | |
if ( $key !== false ) | |
{ | |
unset($index[$identifier]); | |
static::$memcached->set($this->config['cache_id'].$sections, $index); | |
} | |
} | |
else | |
{ | |
// create a new key if needed | |
$key === false and $key = $this->_new_key(); | |
} | |
return $key; | |
} | |
/** | |
* Generate a new unique key for the current identifier | |
* | |
* @return string | |
*/ | |
protected function _new_key() | |
{ | |
$key = ''; | |
while (strlen($key) < 32) | |
{ | |
$key .= mt_rand(0, mt_getrandmax()); | |
} | |
return md5($this->config['cache_id'].'_'.uniqid($key, TRUE)); | |
} | |
/** | |
* Get the section index | |
* | |
* @return array containing the identifier, the sections, and the section index | |
*/ | |
protected function _get_index() | |
{ | |
// get the section name and identifier | |
$sections = explode('.', $this->identifier); | |
if (count($sections) > 1) | |
{ | |
$identifier = array_pop($sections); | |
$sections = '.'.implode('.', $sections); | |
} | |
else | |
{ | |
$identifier = $this->identifier; | |
$sections = ''; | |
} | |
// get the cache index and return it | |
return array($identifier, $sections, static::$memcached->get($this->config['cache_id'].$sections)); | |
} | |
/** | |
* Update the section index | |
* | |
* @param string cache key | |
*/ | |
protected function _update_index($key) | |
{ | |
// get the current index information | |
list($identifier, $sections, $index) = $this->_get_index(); | |
// create a new index and store the key | |
is_array($index) or $index = array(); | |
// store the key in the index and write the index back | |
$index[$identifier] = array($key, $this->created); | |
static::$memcached->set($this->config['cache_id'].$sections, $index, 0); | |
// get the directory index | |
$index = static::$memcached->get($this->config['cache_id'].'__DIR__'); | |
if (is_array($index)) | |
{ | |
if (!in_array($this->config['cache_id'].$sections, $index)) | |
{ | |
$index[] = $this->config['cache_id'].$sections; | |
} | |
} | |
else | |
{ | |
$index = array($this->config['cache_id'].$sections); | |
} | |
// update the directory index | |
static::$memcached->set($this->config['cache_id'].'__DIR__', $index, 0); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment