Added Redis support to Cache layer

This commit is contained in:
Josh Sherman 2013-02-28 15:14:10 -05:00
parent 6a1cf8c035
commit 026c8137e9
2 changed files with 348 additions and 109 deletions

View file

@ -35,8 +35,13 @@
*/ */
class Cache extends Object class Cache extends Object
{ {
/**
* Cache handler
*
* @access private
* @var string memcached or redis
*/
private $handler = null; private $handler = null;
private $datasource = null;
/** /**
* Hostname for the Memcached Server * Hostname for the Memcached Server
@ -81,12 +86,9 @@ class Cache extends Object
/** /**
* Constructor * Constructor
* *
* Sets up our connection variables. * Sets up our variables
*
* @param string $hostname optional hostname to connect to
* @param string $database optional port to use
*/ */
public function __construct($hostname = null, $port = null) public function __construct()
{ {
parent::__construct(); parent::__construct();
@ -101,27 +103,30 @@ class Cache extends Object
throw new Exception('You must specify the datasource\'s type'); throw new Exception('You must specify the datasource\'s type');
} }
$this->hostname = isset($datasource['hostname']) ? $datasource['hostname'] : 'localhost';
switch ($datasource['type']) switch ($datasource['type'])
{ {
case 'memcache': case 'memcache':
case 'memcached': case 'memcached':
$this->handler = 'memcached'; $this->handler = 'memcached';
$this->port = isset($datasource['port']) ? $datasource['port'] : 11211;
break; break;
case 'redis': case 'redis':
$this->handler = 'redis'; $this->handler = 'redis';
$this->port = isset($datasource['port']) ? $datasource['port'] : 6379;
$this->database = isset($datasource['database']) ? $datasource['database'] : 0;
break; break;
default: default:
throw new Exception('The specified datasource type "' . $datasource['type'] . '" is unsupported.'); throw new Exception('The specified datasource type "' . $datasource['type'] . '" is unsupported.');
} }
if ($this->namespace != '') if (isset($datasource['namespace']) && $datasource['namespace'] != '')
{ {
$this->namespace .= ':'; $this->namespace = $datasource['namespace'] . ':';
} }
$this->datasource = $datasource;
} }
} }
} }
@ -162,11 +167,62 @@ class Cache extends Object
{ {
if ($this->connection === null) if ($this->connection === null)
{ {
$this->connection = new Memcache(); switch ($this->handler)
$this->connection->connect($this->hostname, $this->port); {
case 'memcached':
$this->connection = new Memcache();
break;
case 'redis':
$this->connection = new Redis();
break;
}
} }
return true; $connected = $this->connection->connect($this->hostname, $this->port);
if ($connected && $this->database != 0)
{
$this->connection->select($this->database);
}
return $connected;
}
/**
* Set Key
*
* Sets key to the specified value.
*
* @param string $key key to set
* @param mixed $value value to set
* @param integer $expiration optional expiration, defaults to 5 minutes
* @return boolean status of writing the data to the key
*/
public function set($key, $value, $expire = 300)
{
$key = strtolower($key);
if ($this->open())
{
switch ($this->handler)
{
case 'memcached':
return $this->connection->set(strtolower($this->namespace . $key), $value, 0, $expire);
break;
case 'redis':
if (is_array($value))
{
$value = 'JSON:' . json_encode($value);
}
return $this->connection->set(strtolower($this->namespace . $key), $value, $expire);
break;
}
}
return false;
} }
/** /**
@ -181,45 +237,53 @@ class Cache extends Object
{ {
if ($this->open()) if ($this->open())
{ {
// Namespaces keys
if (is_array($keys)) if (is_array($keys))
{ {
foreach ($keys as $index => $key) foreach ($keys as $index => $key)
{ {
$keys[$index] = strtoupper($this->namespace . $key); $keys[$index] = strtolower($this->namespace . $key);
} }
} }
else else
{ {
$keys = strtoupper($this->namespace . $keys); $keys = strtolower($this->namespace . $keys);
} }
return $this->connection->get($keys); switch ($this->handler)
} {
case 'memcached':
return $this->connection->get($keys);
break;
return false; case 'redis':
} if (is_array($keys))
{
$values = $this->connection->mGet($keys);
/** foreach ($values as $index => $value)
* Set Key {
* if (substr($value, 0, 5) == 'JSON:')
* Sets key to the specified value. I've found that compression can lead to {
* issues with integers and can slow down the storage and retrieval of data $values[$index] = json_decode(substr($value, 5), true);
* (defeats the purpose of caching if you ask me) and isn't supported. I've }
* also been burned by data inadvertantly being cached for infinity, hence }
* the 5 minute default.
*
* @param string $key key to set
* @param mixed $value value to set
* @param integer $expiration optional expiration, defaults to 5 minutes
* @return boolean status of writing the data to the key
*/
public function set($key, $value, $expire = 300)
{
$key = strtoupper($key);
if ($this->open()) return $values;
{ }
return $this->connection->set(strtoupper($this->namespace . $key), $value, 0, $expire); else
{
$value = $this->connection->get($keys);
if (substr($value, 0, 5) == 'JSON:')
{
$value = json_decode(substr($value, 5), true);
}
return $value;
}
break;
}
} }
return false; return false;
@ -245,7 +309,7 @@ class Cache extends Object
// Memcache() doesn't let you pass an array to delete all records the same way you can with get() // Memcache() doesn't let you pass an array to delete all records the same way you can with get()
foreach ($keys as $key) foreach ($keys as $key)
{ {
$this->connection->delete(strtoupper($this->namespace . $key)); $this->connection->delete(strtolower($this->namespace . $key));
} }
return true; return true;
@ -254,23 +318,83 @@ class Cache extends Object
return false; return false;
} }
/**
* Change
*
* Increment / decrement a variable by a value
*
* @param string $direction increment or decrement
* @param string $key key to increment
* @param integer $value increment by value
* @return mixed new value or false if unable to connect
*/
public function change($direction, $key, $value = 1)
{
if ($this->handler == 'redis')
{
switch ($direction)
{
case 'increment': $direction = 'incr'; break;
case 'decrement': $direction = 'decr'; break;
}
if ($value > 1)
{
$direction .= 'By';
}
}
$key = strtolower($this->namespace . $key);
if ($this->open())
{
// Memcache::*crement() doesn't create the key
if ($this->handler == 'memcached')
{
if ($this->connection->add($key, $value) === false)
{
return $this->connection->$direction($key, $value);
}
else
{
return $value;
}
}
else
{
return $this->connection->$direction($key, $value);
}
}
return false;
}
/** /**
* Increment Key * Increment Key
* *
* Increments the value of an existing key. * Increments the value of an existing key.
* *
* @param string $key key to increment * @param string $key key to increment
* @return boolean status of incrementing the key * @param integer $value increment by value
* @todo Wondering if I should check the key and set to 1 if it's new * @return mixed new value or false if unable to connect
*/ */
public function increment($key) public function increment($key, $value = 1)
{ {
if ($this->open()) return $this->change('increment', $key, $value);
{ }
return $this->connection->increment(strtoupper($this->namespace . $key));
}
return false; /**
* Decrement Key
*
* Decrements the value of an existing key.
*
* @param string $key key to decrement
* @param integer $value decrement by value
* @return mixed new value or false if unable to connect
*/
public function decrement($key, $value = 1)
{
return $this->change('decrement', $key, $value);
} }
} }

231
jar.php
View file

@ -614,8 +614,13 @@ class Browser extends Object
*/ */
class Cache extends Object class Cache extends Object
{ {
/**
* Cache handler
*
* @access private
* @var string memcached or redis
*/
private $handler = null; private $handler = null;
private $datasource = null;
/** /**
* Hostname for the Memcached Server * Hostname for the Memcached Server
@ -660,12 +665,9 @@ class Cache extends Object
/** /**
* Constructor * Constructor
* *
* Sets up our connection variables. * Sets up our variables
*
* @param string $hostname optional hostname to connect to
* @param string $database optional port to use
*/ */
public function __construct($hostname = null, $port = null) public function __construct()
{ {
parent::__construct(); parent::__construct();
@ -680,38 +682,32 @@ class Cache extends Object
throw new Exception('You must specify the datasource\'s type'); throw new Exception('You must specify the datasource\'s type');
} }
$this->hostname = isset($datasource['hostname']) ? $datasource['hostname'] : 'localhost';
switch ($datasource['type']) switch ($datasource['type'])
{ {
case 'memcache': case 'memcache':
case 'memcached': case 'memcached':
$this->handler = 'memcached'; $this->handler = 'memcached';
$this->port = isset($datasource['port']) ? $datasource['port'] : 11211;
break; break;
case 'redis': case 'redis':
$this->handler = 'redis'; $this->handler = 'redis';
$this->port = isset($datasource['port']) ? $datasource['port'] : 6379;
$this->database = isset($datasource['database']) ? $datasource['database'] : 0;
break; break;
default: default:
throw new Exception('The specified datasource type "' . $datasource['type'] . '" is unsupported.'); throw new Exception('The specified datasource type "' . $datasource['type'] . '" is unsupported.');
} }
var_dump($datasource); if (isset($datasource['namespace']) && $datasource['namespace'] != '')
exit;
foreach (array('hostname', 'port', 'database', 'namespace') as $variable)
{ {
if (isset($datasource[$variable])) $this->namespace = $datasource['namespace'] . ':';
{
$this->$variable = $datasource[$variable];
}
} }
} }
} }
if ($this->namespace != '')
{
$this->namespace .= ':';
}
} }
/** /**
@ -750,11 +746,62 @@ class Cache extends Object
{ {
if ($this->connection === null) if ($this->connection === null)
{ {
$this->connection = new Memcache(); switch ($this->handler)
$this->connection->connect($this->hostname, $this->port); {
case 'memcached':
$this->connection = new Memcache();
break;
case 'redis':
$this->connection = new Redis();
break;
}
} }
return true; $connected = $this->connection->connect($this->hostname, $this->port);
if ($connected && $this->database != 0)
{
$this->connection->select($this->database);
}
return $connected;
}
/**
* Set Key
*
* Sets key to the specified value.
*
* @param string $key key to set
* @param mixed $value value to set
* @param integer $expiration optional expiration, defaults to 5 minutes
* @return boolean status of writing the data to the key
*/
public function set($key, $value, $expire = 300)
{
$key = strtolower($key);
if ($this->open())
{
switch ($this->handler)
{
case 'memcached':
return $this->connection->set(strtolower($this->namespace . $key), $value, 0, $expire);
break;
case 'redis':
if (is_array($value))
{
$value = 'JSON:' . json_encode($value);
}
return $this->connection->set(strtolower($this->namespace . $key), $value, $expire);
break;
}
}
return false;
} }
/** /**
@ -769,45 +816,53 @@ class Cache extends Object
{ {
if ($this->open()) if ($this->open())
{ {
// Namespaces keys
if (is_array($keys)) if (is_array($keys))
{ {
foreach ($keys as $index => $key) foreach ($keys as $index => $key)
{ {
$keys[$index] = strtoupper($this->namespace . $key); $keys[$index] = strtolower($this->namespace . $key);
} }
} }
else else
{ {
$keys = strtoupper($this->namespace . $keys); $keys = strtolower($this->namespace . $keys);
} }
return $this->connection->get($keys); switch ($this->handler)
} {
case 'memcached':
return $this->connection->get($keys);
break;
return false; case 'redis':
} if (is_array($keys))
{
$values = $this->connection->mGet($keys);
/** foreach ($values as $index => $value)
* Set Key {
* if (substr($value, 0, 5) == 'JSON:')
* Sets key to the specified value. I've found that compression can lead to {
* issues with integers and can slow down the storage and retrieval of data $values[$index] = json_decode(substr($value, 5), true);
* (defeats the purpose of caching if you ask me) and isn't supported. I've }
* also been burned by data inadvertantly being cached for infinity, hence }
* the 5 minute default.
*
* @param string $key key to set
* @param mixed $value value to set
* @param integer $expiration optional expiration, defaults to 5 minutes
* @return boolean status of writing the data to the key
*/
public function set($key, $value, $expire = 300)
{
$key = strtoupper($key);
if ($this->open()) return $values;
{ }
return $this->connection->set(strtoupper($this->namespace . $key), $value, 0, $expire); else
{
$value = $this->connection->get($keys);
if (substr($value, 0, 5) == 'JSON:')
{
$value = json_decode(substr($value, 5), true);
}
return $value;
}
break;
}
} }
return false; return false;
@ -833,7 +888,7 @@ class Cache extends Object
// Memcache() doesn't let you pass an array to delete all records the same way you can with get() // Memcache() doesn't let you pass an array to delete all records the same way you can with get()
foreach ($keys as $key) foreach ($keys as $key)
{ {
$this->connection->delete(strtoupper($this->namespace . $key)); $this->connection->delete(strtolower($this->namespace . $key));
} }
return true; return true;
@ -842,23 +897,83 @@ class Cache extends Object
return false; return false;
} }
/**
* Change
*
* Increment / decrement a variable by a value
*
* @param string $direction increment or decrement
* @param string $key key to increment
* @param integer $value increment by value
* @return mixed new value or false if unable to connect
*/
public function change($direction, $key, $value = 1)
{
if ($this->handler == 'redis')
{
switch ($direction)
{
case 'increment': $direction = 'incr'; break;
case 'decrement': $direction = 'decr'; break;
}
if ($value > 1)
{
$direction .= 'By';
}
}
$key = strtolower($this->namespace . $key);
if ($this->open())
{
// Memcache::*crement() doesn't create the key
if ($this->handler == 'memcached')
{
if ($this->connection->add($key, $value) === false)
{
return $this->connection->$direction($key, $value);
}
else
{
return $value;
}
}
else
{
return $this->connection->$direction($key, $value);
}
}
return false;
}
/** /**
* Increment Key * Increment Key
* *
* Increments the value of an existing key. * Increments the value of an existing key.
* *
* @param string $key key to increment * @param string $key key to increment
* @return boolean status of incrementing the key * @param integer $value increment by value
* @todo Wondering if I should check the key and set to 1 if it's new * @return mixed new value or false if unable to connect
*/ */
public function increment($key) public function increment($key, $value = 1)
{ {
if ($this->open()) return $this->change('increment', $key, $value);
{ }
return $this->connection->increment(strtoupper($this->namespace . $key));
}
return false; /**
* Decrement Key
*
* Decrements the value of an existing key.
*
* @param string $key key to decrement
* @param integer $value decrement by value
* @return mixed new value or false if unable to connect
*/
public function decrement($key, $value = 1)
{
return $this->change('decrement', $key, $value);
} }
} }