Find this useful? Enter your email to receive occasional updates for securing PHP code.
Signing you up...
Thank you for signing up!
PHP Decode
<?php namespace Memcached; use \Exception; use \Memcached; /** * Wrapper for Mem..
Decoded Output download
<?php
namespace Memcached;
use \Exception;
use \Memcached;
/**
* Wrapper for Memcached with local storage support
*
* @throws Exception
* @package Library
* @subpackage Cache
* @see http://pecl.php.net/package/memcached
* @see http://pecl.php.net/package/igbinary
* @author Aleksey Korzun <[email protected]>
* @version 0.2
* @license MIT
* @link https://github.com/AlekseyKorzun/memcached-wrapper-php
* @link http://www.alekseykorzun.com
*/
class Wrapper
{
/**
* Dog-pile prevention delay in seconds, adjust if you have a constant miss
* of cache after expiration because un-cached call takes more then specified
* delay
*
* @var int
*/
const DELAY = 600;
/**
* Default life time of all caches wrapper creates in seconds
*
* @var int
*/
const DEFAULT_TTL = 3600;
/**
* Parent expiration padding (so internal cache stamp does not expire before
* the actual cache) in seconds
*
* If expiration of your keys is set below this number you will
* not benefit from this optimization.
*
* @var int
*/
const EXTENDED_TTL = 300;
/**
* Instance of Memcached
*
* @var Memcached
*/
protected $memcached;
/**
* Local storage
*
* @var array
*/
protected $storage = array();
/**
* Current setting for local storage
*
* @var bool
*/
protected $isStorageEnabled = true;
/**
* Indicates that current look-up will expire shortly (dog-pile)
*
* @var bool
*/
protected $isResourceExpired = false;
/**
* Marks current request as invalid (not-found, etc)
*
* @var bool
*/
protected $isResourceInvalid = false;
/**
* Dogpile protection (wraps your cached resources into metadata array with an internal time stamp)
*
* @var bool
*/
protected $isProtected = true;
/**
* Cache activation switch
*
* @var bool
*/
protected $isActive = true;
/**
* Class initializer, creates a new singleton instance of Memcached
* with optimized configuration
*
* @throws Exception we want to bail if Memcached extension is not loaded or
* if passed server list is invalid
* @param string $pool create an instance of cache client with a specfic pool
* @param mixed[] $servers a list of Memcached servers we will be using, each entry
* in servers is supposed to be an array containing hostname, port, and
* optionally, weight of the server
*
* Example:
*
* $servers = array(
* array('mem1.domain.com', 11211, 33),
* array('mem2.domain.com', 11211, 67)
* );
*
* See: http://www.php.net/manual/en/memcached.addservers.php
*/
public function __construct($pool = null, array $servers = null)
{
// Make sure extension is available at the run-time
if (!extension_loaded('memcached')) {
throw new Exception('Memcached extension failed to load.');
}
// Validate passed server list
if (!is_null($servers)) {
$this->validateServers($servers);
}
// Create a new Memcached instance and set optimized options
$this->memcached = new Memcached($pool);
// Use faster compression if available
if (Memcached::HAVE_IGBINARY) {
$this->instance()->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY);
}
$this->instance()->setOption(Memcached::OPT_DISTRIBUTION, Memcached::DISTRIBUTION_CONSISTENT);
$this->instance()->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
$this->instance()->setOption(Memcached::OPT_NO_BLOCK, true);
$this->instance()->setOption(Memcached::OPT_TCP_NODELAY, true);
$this->instance()->setOption(Memcached::OPT_COMPRESSION, true);
$this->instance()->setOption(Memcached::OPT_CONNECT_TIMEOUT, 2);
if (!is_null($servers)) {
// Since we are using persistent connections, make sure servers are not
// reloaded
if (!count($this->instance()->getServerList())) {
$this->instance()->addServers($servers);
}
}
}
/**
* Delete key(s) from cache and local storage
*
* @param string[]|string $keys array of keys to delete or a single key
* @return bool returns false if it failed to delete any of the keys
*/
public function delete($keys)
{
// Convert keys to an array
if (!is_array($keys)) {
$keys = array($keys);
}
if ($keys) {
foreach ($keys as $key) {
// Attempt to remove data from Memcached pool
if (!$this->instance()->delete($key)) {
// If we were unable to remove it only care if the error wasn't RES_NOTFOUND
if ($this->instance()->getResultCode() != Memcached::RES_NOTFOUND) {
return false;
}
}
// Also purge from our instance upon successful removal or if item
// is no longer stored in our cache pool
if ($this->isStored($key)) {
unset($this->storage[$key]);
}
}
return true;
}
return false;
}
/**
* Replace resource associated with an existing key with something else
*
* @throws Exception if the key is over 250 bytes
* @param string $key key to replace value of
* @param mixed $resource resource you want existing value to be replaced with
* @return bool
*/
public function replace($key, $resource, $ttl = self::DEFAULT_TTL)
{
// If caching is turned off return false
if (!$this->isActive()) {
return false;
}
// Make sure we are under the proper limit
if (strlen($this->instance()->getOption(Memcached::OPT_PREFIX_KEY) . $key) > 250) {
throw new Exception('The passed cache key is over 250 bytes');
}
// If protection is enabled, wrap the resource
if ($this->isProtected()) {
$resource = $this->wrap($resource, $ttl);
}
// Save our data within cache poo
if ($this->instance()->replace($key, $resource, $ttl)) {
// Attempt to store data locally, unwrap method takes care of it for protected resources
if ($this->isProtected()) {
$this->unwrap($key, $resource);
} else {
$this->store($key, $resource);
}
return true;
}
return false;
}
/**
* Add a new cached record using passed resource and key association, this
* method will return false if key already exists (unlike set)
*
* @throws Exception if the key is over 250 bytes
* @param string $key key to store passed resource under
* @param mixed $resource resource you want to cache
* @param int $ttl when should this key expire in seconds
* @return bool
*/
public function add($key, $resource, $ttl = self::DEFAULT_TTL)
{
// If caching is turned off return false
if (!$this->isActive()) {
return false;
}
// Make sure we are under the proper limit
if (strlen($this->instance()->getOption(Memcached::OPT_PREFIX_KEY) . $key) > 250) {
throw new Exception('The passed cache key is over 250 bytes');
}
// If protection is enabled, wrap the resource
if ($this->isProtected()) {
$resource = $this->wrap($resource, $ttl);
}
// Save our data within cache pool
if ($this->instance()->add($key, $resource, $ttl)) {
// Attempt to store data locally, unwrap method takes care of it for protected resources
if ($this->isProtected()) {
$this->unwrap($key, $resource);
} else {
$this->store($key, $resource);
}
return true;
}
return false;
}
/**
* Add a new cached record using passed resource and key association
*
* @throws Exception if the key is over 250 bytes
* @param string $key key to store passed resource under
* @param mixed $resource resource you want to cache
* @param int $ttl when should this key expire in seconds
* @return bool
*/
public function set($key, $resource, $ttl = self::DEFAULT_TTL)
{
// If caching is turned off return false
if (!$this->isActive()) {
return false;
}
// Make sure we are under the proper limit
if (strlen($this->instance()->getOption(Memcached::OPT_PREFIX_KEY) . $key) > 250) {
throw new Exception('The passed cache key is over 250 bytes');
}
// If protection is enabled, wrap the resource
if ($this->isProtected()) {
$resource = $this->wrap($resource, $ttl);
}
// Save our data within cache pool
if ($this->instance()->set($key, $resource, $ttl)) {
// Attempt to store data locally, unwrap method takes care of it for protected resources
if ($this->isProtected()) {
$this->unwrap($key, $resource);
} else {
$this->store($key, $resource);
}
return true;
}
return false;
}
/**
* Retrieve data based on provided key from cache pool, this method
* will call either getArray or getSimple depending on amount of keys
* requested.
*
* @param string[]|string $keys an array of keys or a single key to look-up from cache
* @param mixed $resource where to store retrieved resource
* @param bool $purge if you wish to remove key from pool after retrieving resource
* associated with this you can pass this as true
* @return bool returns true on a successful request, false on a failure or forced
* expiration
*/
public function get($keys, &$resource, $purge = false)
{
// If caching is turned off return false
if (!$this->isActive()) {
return false;
}
// Remove expired flag and make sure item is set to valid
$this->isResourceExpired = $this->isResourceInvalid = false;
// Prevent multi-get requests if array only contains a single key
if (is_array($keys)) {
if (count($keys) == 1) {
$keys = array_shift($keys);
$isDiverted = true;
}
}
// Determine method of retrieval
$resource = (is_array($keys)
? $this->getArray($keys)
: $this->getSimple($keys));
// If multi get by-pass is activated, convert result to an array
if (isset($isDiverted)) {
$resource = array($resource);
}
// If key is marked as expired (needs to be updated within this request)
// or not found return false
if ($this->isResourceExpired || $this->isResourceInvalid) {
return false;
}
// If purge was passed, delete requested resource
if ($purge) {
$this->delete($keys);
}
return true;
}
/**
* Retrieve multiple resources from cache pool and/or local storage
*
* @param string[] $keys array of keys to look-up from cache
* @return mixed[]|bool returns array of retrieved resources or false
* if look up fails
*/
protected function getArray(array $keys)
{
// Initialize variables
$results = array();
// Check local storage first
if ($this->isStorageEnabled()) {
foreach ($keys as $pointer => $key) {
if ($this->isStored($key)) {
$results[$key] = $this->storage[$key];
unset($keys[$pointer]);
continue;
}
}
}
// All results were retrieved within local storage
if (empty($keys)) {
return $results;
}
// Look up keys within cache pool
$resources = $this->instance()->getMulti(array_values($keys));
if ($this->instance()->getResultCode() == Memcached::RES_SUCCESS) {
foreach ($resources as $key => $resource) {
if ($this->isProtected()) {
$results[$key] = $this->unwrap($key, $resource);
continue;
}
$results[$key] = $resource;
}
}
// If we got some of the results, let's return them
if ($results) {
return $results;
}
// Mark resource as invalid
$this->isResourceInvalid = true;
return false;
}
/**
* Retrieve single resource from cache pool / local storage
*
* @param string $key key to look-up from cache
* @return mixed|bool returns cached resource or false on failure
*/
protected function getSimple($key)
{
// Attempt to retrieve record within local storage
if ($this->isStorageEnabled()) {
if ($this->isStored($key)) {
return $this->storage[$key];
}
}
// Attempt to retrieve record within cache pool
$resource = $this->instance()->get($key);
if ($this->instance()->getResultCode() == Memcached::RES_SUCCESS) {
if ($this->isProtected()) {
return $this->unwrap($key, $resource);
}
return $resource;
}
// Mark requested resource as invalid
$this->isResourceInvalid = true;
return false;
}
/**
* Get requested data back into memory while setting a delayed cache entry
* if data is expiring soon
*
* @see http://highscalability.com/strategy-break-memcache-dog-pile
* @param string $key key that you are retrieving
* @param mixed[] packed data that we got back from cache pool
* @return mixed|bool returns cached resource or false if invalid data was
* passed for unwrapping
*/
protected function unwrap($key, array $data)
{
// Enforce that data we get back was previously packed
if (!isset($data['ttl']) || !isset($data['resource'])) {
return false;
}
if ($data['ttl'] > 0) {
if (time() >= $data['ttl']) {
// Set the stale value back into cache for a short 'delay'
// so no one else tries to write the same data
if ($this->instance()->set($key, $this->wrap($data['resource'], self::DELAY), self::DELAY)) {
$this->isResourceExpired = true;
}
}
}
return $this->store($key, $data['resource']);
}
/**
* Wrap new cached resource into an array containing TTL stamp
*
* @see http://highscalability.com/strategy-break-memcache-dog-pile
* @param mixed $resource resource that is getting cached
* @param int $ttl internal extended expiration
* @return mixed[] returns packed resource with TTL stamp to store in cache
*/
protected function wrap($resource, $ttl)
{
// The actual cache time must be padded in order to properly maintain internal
// cache expiration system
if ($ttl) {
// If unix time stamp is passed as TTL make sure we properly handle it
if ($ttl < 60 * 60 * 24 * 30) {
$ttl += time();
}
// If extended TTL is greater than key TTL, skip optimization
if (($ttl - self::EXTENDED_TTL) > time()) {
$ttl -= self::EXTENDED_TTL;
} else {
$ttl = 0;
}
}
return array(
'ttl' => $ttl,
'resource' => $resource
);
}
/**
* Check if passed key is stored using local store
*
* @param string $key
* @return bool
*/
public function isStored($key)
{
return (bool)isset($this->storage[$key]);
}
/**
* Store data locally
*
* @param string $key key to save resource under
* @param mixed $resource what you are storing in cache
* @return mixed resource that we attempted to store
*/
protected function store($key, $resource)
{
if ($this->isStorageEnabled()) {
$this->storage[$key] = $resource;
}
return $resource;
}
/**
* Checks if local storage is enabled
*
* @return bool returns true if local storage is enabled false otherwise
*/
public function isStorageEnabled()
{
return (bool)$this->isStorageEnabled;
}
/**
* Enable local storage
*/
public function enableStorage()
{
$this->isStorageEnabled = true;
}
/**
* Toggle local storage
*/
public function toggleStorage()
{
$this->isStorageEnabled = (bool)!$this->isStorageEnabled;
}
/**
* Disable local storage
*/
public function disableStorage()
{
$this->isStorageEnabled = false;
}
/**
* Check if race condition protection is enabled
*
* @return bool returns true if protection is enabled
*/
public function isProtected()
{
return (bool)$this->isProtected;
}
/**
* Enable race condition protection
*/
public function enableProtection()
{
$this->isProtected = true;
}
/**
* Toggle race condition protection
*/
public function toggleProtection()
{
$this->isProtected = (bool)!$this->isProtected;
}
/**
* Disable race condition protection
*/
public function disableProtection()
{
$this->isProtected = false;
}
/**
* Check if data compression is enabled
*
* @return bool returns true if data compression is enabled
*/
public function isCompressed()
{
return (bool)$this->instance()->getOption(Memcached::OPT_COMPRESSION);
}
/**
* Enable data compression
*/
public function enableCompression()
{
$this->instance()->setOption(Memcached::OPT_COMPRESSION, true);
}
/**
* Toggle data compression
*/
public function toggleCompression()
{
$this->instance()->setOption(
Memcached::OPT_COMPRESSION,
(bool)!$this->instance()->getOption(Memcached::OPT_COMPRESSION)
);
}
/**
* Disable data compression
*/
public function disableCompression()
{
$this->instance()->setOption(Memcached::OPT_COMPRESSION, false);
}
/**
* Toggle all of the custom options
*/
public function toggleAll()
{
$this->toggleStorage();
$this->toggleCompression();
$this->toggleProtection();
}
/**
* Deactivate caching
*/
public function deactivate()
{
$this->isActive = false;
}
/**
* Activate caching
*/
public function activate()
{
$this->isActive = true;
}
/**
* Check if caching is currently active
*
* @return bool returns true if caching is active otherwise false
*/
public function isActive()
{
return (bool)$this->isActive;
}
/**
* Validate array of cache servers that should be loaded to Memcached extension
*
* @throws Exception if we detect something out of specification
* @param mixed[] $servers a list of Memcached servers we will be using
*/
public function validateServers(array $servers)
{
if ($servers) {
foreach ($servers as $key => $server) {
// Check number of parameters associated with passed server
if (!is_array($server) || count($server) < 2 || count($server) > 3) {
throw new Exception(
'Invalid server parameters found in passed server array on key ' . $key . ', please see'
. ' http://www.php.net/manual/en/memcached.addservers.php'
);
}
// Auto adjust weight of servers
if (count($server) == 2) {
$server[] = 100;
}
list($ip, $port, $weight) = $server;
// Check port and weight
if (!is_numeric($port) || (!is_null($weight) && !is_numeric($weight))) {
throw new Exception(
'Invalid server port and/or weight found in passed server array on key ' . $key . ', please see'
. ' http://www.php.net/manual/en/memcached.addservers.php'
);
}
}
}
}
/**
* Retrieve instance of Memcached client
*
* @return Memcached
*/
public function instance()
{
return $this->memcached;
}
/**
* Pass all method calls directly to instance of Memcached
*
* @param string $name method that was invoked
* @param mixed[] $arguments arguments that were passed to invoked method
* @return mixed
*/
public function __call($name, $arguments)
{
// Methods we currently do not support
$blacklist = array(
'getMultiByKey',
'replaceByKey',
'setByKey',
'setMulti',
'setMultiByKey'
);
if (in_array($name, $blacklist)) {
throw new Exception(
'Requested method is currently not supported'
);
}
// Methods that should not be protected/compressed/covered by local storage
$unprotected = array(
'prependByKey',
'appendByKey',
'append',
'prepend',
'increment',
'decrement',
'decrementByKey',
'incrementByKey',
);
if (in_array($name, $unprotected)) {
if ($this->isProtected()) {
throw new Exception(
'Please turn off race condition protection when using this method.'
);
}
if ($this->isStorageEnabled()) {
throw new Exception(
'Please turn off storage when using this method.'
);
}
if ($this->isCompressed()) {
throw new Exception(
'Please turn off compression when using this method.'
);
}
}
return call_user_func_array(
array(
$this->instance(),
$name
),
$arguments
);
}
}
?>
Did this file decode correctly?
Original Code
<?php
namespace Memcached;
use \Exception;
use \Memcached;
/**
* Wrapper for Memcached with local storage support
*
* @throws Exception
* @package Library
* @subpackage Cache
* @see http://pecl.php.net/package/memcached
* @see http://pecl.php.net/package/igbinary
* @author Aleksey Korzun <[email protected]>
* @version 0.2
* @license MIT
* @link https://github.com/AlekseyKorzun/memcached-wrapper-php
* @link http://www.alekseykorzun.com
*/
class Wrapper
{
/**
* Dog-pile prevention delay in seconds, adjust if you have a constant miss
* of cache after expiration because un-cached call takes more then specified
* delay
*
* @var int
*/
const DELAY = 600;
/**
* Default life time of all caches wrapper creates in seconds
*
* @var int
*/
const DEFAULT_TTL = 3600;
/**
* Parent expiration padding (so internal cache stamp does not expire before
* the actual cache) in seconds
*
* If expiration of your keys is set below this number you will
* not benefit from this optimization.
*
* @var int
*/
const EXTENDED_TTL = 300;
/**
* Instance of Memcached
*
* @var Memcached
*/
protected $memcached;
/**
* Local storage
*
* @var array
*/
protected $storage = array();
/**
* Current setting for local storage
*
* @var bool
*/
protected $isStorageEnabled = true;
/**
* Indicates that current look-up will expire shortly (dog-pile)
*
* @var bool
*/
protected $isResourceExpired = false;
/**
* Marks current request as invalid (not-found, etc)
*
* @var bool
*/
protected $isResourceInvalid = false;
/**
* Dogpile protection (wraps your cached resources into metadata array with an internal time stamp)
*
* @var bool
*/
protected $isProtected = true;
/**
* Cache activation switch
*
* @var bool
*/
protected $isActive = true;
/**
* Class initializer, creates a new singleton instance of Memcached
* with optimized configuration
*
* @throws Exception we want to bail if Memcached extension is not loaded or
* if passed server list is invalid
* @param string $pool create an instance of cache client with a specfic pool
* @param mixed[] $servers a list of Memcached servers we will be using, each entry
* in servers is supposed to be an array containing hostname, port, and
* optionally, weight of the server
*
* Example:
*
* $servers = array(
* array('mem1.domain.com', 11211, 33),
* array('mem2.domain.com', 11211, 67)
* );
*
* See: http://www.php.net/manual/en/memcached.addservers.php
*/
public function __construct($pool = null, array $servers = null)
{
// Make sure extension is available at the run-time
if (!extension_loaded('memcached')) {
throw new Exception('Memcached extension failed to load.');
}
// Validate passed server list
if (!is_null($servers)) {
$this->validateServers($servers);
}
// Create a new Memcached instance and set optimized options
$this->memcached = new Memcached($pool);
// Use faster compression if available
if (Memcached::HAVE_IGBINARY) {
$this->instance()->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY);
}
$this->instance()->setOption(Memcached::OPT_DISTRIBUTION, Memcached::DISTRIBUTION_CONSISTENT);
$this->instance()->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
$this->instance()->setOption(Memcached::OPT_NO_BLOCK, true);
$this->instance()->setOption(Memcached::OPT_TCP_NODELAY, true);
$this->instance()->setOption(Memcached::OPT_COMPRESSION, true);
$this->instance()->setOption(Memcached::OPT_CONNECT_TIMEOUT, 2);
if (!is_null($servers)) {
// Since we are using persistent connections, make sure servers are not
// reloaded
if (!count($this->instance()->getServerList())) {
$this->instance()->addServers($servers);
}
}
}
/**
* Delete key(s) from cache and local storage
*
* @param string[]|string $keys array of keys to delete or a single key
* @return bool returns false if it failed to delete any of the keys
*/
public function delete($keys)
{
// Convert keys to an array
if (!is_array($keys)) {
$keys = array($keys);
}
if ($keys) {
foreach ($keys as $key) {
// Attempt to remove data from Memcached pool
if (!$this->instance()->delete($key)) {
// If we were unable to remove it only care if the error wasn't RES_NOTFOUND
if ($this->instance()->getResultCode() != Memcached::RES_NOTFOUND) {
return false;
}
}
// Also purge from our instance upon successful removal or if item
// is no longer stored in our cache pool
if ($this->isStored($key)) {
unset($this->storage[$key]);
}
}
return true;
}
return false;
}
/**
* Replace resource associated with an existing key with something else
*
* @throws Exception if the key is over 250 bytes
* @param string $key key to replace value of
* @param mixed $resource resource you want existing value to be replaced with
* @return bool
*/
public function replace($key, $resource, $ttl = self::DEFAULT_TTL)
{
// If caching is turned off return false
if (!$this->isActive()) {
return false;
}
// Make sure we are under the proper limit
if (strlen($this->instance()->getOption(Memcached::OPT_PREFIX_KEY) . $key) > 250) {
throw new Exception('The passed cache key is over 250 bytes');
}
// If protection is enabled, wrap the resource
if ($this->isProtected()) {
$resource = $this->wrap($resource, $ttl);
}
// Save our data within cache poo
if ($this->instance()->replace($key, $resource, $ttl)) {
// Attempt to store data locally, unwrap method takes care of it for protected resources
if ($this->isProtected()) {
$this->unwrap($key, $resource);
} else {
$this->store($key, $resource);
}
return true;
}
return false;
}
/**
* Add a new cached record using passed resource and key association, this
* method will return false if key already exists (unlike set)
*
* @throws Exception if the key is over 250 bytes
* @param string $key key to store passed resource under
* @param mixed $resource resource you want to cache
* @param int $ttl when should this key expire in seconds
* @return bool
*/
public function add($key, $resource, $ttl = self::DEFAULT_TTL)
{
// If caching is turned off return false
if (!$this->isActive()) {
return false;
}
// Make sure we are under the proper limit
if (strlen($this->instance()->getOption(Memcached::OPT_PREFIX_KEY) . $key) > 250) {
throw new Exception('The passed cache key is over 250 bytes');
}
// If protection is enabled, wrap the resource
if ($this->isProtected()) {
$resource = $this->wrap($resource, $ttl);
}
// Save our data within cache pool
if ($this->instance()->add($key, $resource, $ttl)) {
// Attempt to store data locally, unwrap method takes care of it for protected resources
if ($this->isProtected()) {
$this->unwrap($key, $resource);
} else {
$this->store($key, $resource);
}
return true;
}
return false;
}
/**
* Add a new cached record using passed resource and key association
*
* @throws Exception if the key is over 250 bytes
* @param string $key key to store passed resource under
* @param mixed $resource resource you want to cache
* @param int $ttl when should this key expire in seconds
* @return bool
*/
public function set($key, $resource, $ttl = self::DEFAULT_TTL)
{
// If caching is turned off return false
if (!$this->isActive()) {
return false;
}
// Make sure we are under the proper limit
if (strlen($this->instance()->getOption(Memcached::OPT_PREFIX_KEY) . $key) > 250) {
throw new Exception('The passed cache key is over 250 bytes');
}
// If protection is enabled, wrap the resource
if ($this->isProtected()) {
$resource = $this->wrap($resource, $ttl);
}
// Save our data within cache pool
if ($this->instance()->set($key, $resource, $ttl)) {
// Attempt to store data locally, unwrap method takes care of it for protected resources
if ($this->isProtected()) {
$this->unwrap($key, $resource);
} else {
$this->store($key, $resource);
}
return true;
}
return false;
}
/**
* Retrieve data based on provided key from cache pool, this method
* will call either getArray or getSimple depending on amount of keys
* requested.
*
* @param string[]|string $keys an array of keys or a single key to look-up from cache
* @param mixed $resource where to store retrieved resource
* @param bool $purge if you wish to remove key from pool after retrieving resource
* associated with this you can pass this as true
* @return bool returns true on a successful request, false on a failure or forced
* expiration
*/
public function get($keys, &$resource, $purge = false)
{
// If caching is turned off return false
if (!$this->isActive()) {
return false;
}
// Remove expired flag and make sure item is set to valid
$this->isResourceExpired = $this->isResourceInvalid = false;
// Prevent multi-get requests if array only contains a single key
if (is_array($keys)) {
if (count($keys) == 1) {
$keys = array_shift($keys);
$isDiverted = true;
}
}
// Determine method of retrieval
$resource = (is_array($keys)
? $this->getArray($keys)
: $this->getSimple($keys));
// If multi get by-pass is activated, convert result to an array
if (isset($isDiverted)) {
$resource = array($resource);
}
// If key is marked as expired (needs to be updated within this request)
// or not found return false
if ($this->isResourceExpired || $this->isResourceInvalid) {
return false;
}
// If purge was passed, delete requested resource
if ($purge) {
$this->delete($keys);
}
return true;
}
/**
* Retrieve multiple resources from cache pool and/or local storage
*
* @param string[] $keys array of keys to look-up from cache
* @return mixed[]|bool returns array of retrieved resources or false
* if look up fails
*/
protected function getArray(array $keys)
{
// Initialize variables
$results = array();
// Check local storage first
if ($this->isStorageEnabled()) {
foreach ($keys as $pointer => $key) {
if ($this->isStored($key)) {
$results[$key] = $this->storage[$key];
unset($keys[$pointer]);
continue;
}
}
}
// All results were retrieved within local storage
if (empty($keys)) {
return $results;
}
// Look up keys within cache pool
$resources = $this->instance()->getMulti(array_values($keys));
if ($this->instance()->getResultCode() == Memcached::RES_SUCCESS) {
foreach ($resources as $key => $resource) {
if ($this->isProtected()) {
$results[$key] = $this->unwrap($key, $resource);
continue;
}
$results[$key] = $resource;
}
}
// If we got some of the results, let's return them
if ($results) {
return $results;
}
// Mark resource as invalid
$this->isResourceInvalid = true;
return false;
}
/**
* Retrieve single resource from cache pool / local storage
*
* @param string $key key to look-up from cache
* @return mixed|bool returns cached resource or false on failure
*/
protected function getSimple($key)
{
// Attempt to retrieve record within local storage
if ($this->isStorageEnabled()) {
if ($this->isStored($key)) {
return $this->storage[$key];
}
}
// Attempt to retrieve record within cache pool
$resource = $this->instance()->get($key);
if ($this->instance()->getResultCode() == Memcached::RES_SUCCESS) {
if ($this->isProtected()) {
return $this->unwrap($key, $resource);
}
return $resource;
}
// Mark requested resource as invalid
$this->isResourceInvalid = true;
return false;
}
/**
* Get requested data back into memory while setting a delayed cache entry
* if data is expiring soon
*
* @see http://highscalability.com/strategy-break-memcache-dog-pile
* @param string $key key that you are retrieving
* @param mixed[] packed data that we got back from cache pool
* @return mixed|bool returns cached resource or false if invalid data was
* passed for unwrapping
*/
protected function unwrap($key, array $data)
{
// Enforce that data we get back was previously packed
if (!isset($data['ttl']) || !isset($data['resource'])) {
return false;
}
if ($data['ttl'] > 0) {
if (time() >= $data['ttl']) {
// Set the stale value back into cache for a short 'delay'
// so no one else tries to write the same data
if ($this->instance()->set($key, $this->wrap($data['resource'], self::DELAY), self::DELAY)) {
$this->isResourceExpired = true;
}
}
}
return $this->store($key, $data['resource']);
}
/**
* Wrap new cached resource into an array containing TTL stamp
*
* @see http://highscalability.com/strategy-break-memcache-dog-pile
* @param mixed $resource resource that is getting cached
* @param int $ttl internal extended expiration
* @return mixed[] returns packed resource with TTL stamp to store in cache
*/
protected function wrap($resource, $ttl)
{
// The actual cache time must be padded in order to properly maintain internal
// cache expiration system
if ($ttl) {
// If unix time stamp is passed as TTL make sure we properly handle it
if ($ttl < 60 * 60 * 24 * 30) {
$ttl += time();
}
// If extended TTL is greater than key TTL, skip optimization
if (($ttl - self::EXTENDED_TTL) > time()) {
$ttl -= self::EXTENDED_TTL;
} else {
$ttl = 0;
}
}
return array(
'ttl' => $ttl,
'resource' => $resource
);
}
/**
* Check if passed key is stored using local store
*
* @param string $key
* @return bool
*/
public function isStored($key)
{
return (bool)isset($this->storage[$key]);
}
/**
* Store data locally
*
* @param string $key key to save resource under
* @param mixed $resource what you are storing in cache
* @return mixed resource that we attempted to store
*/
protected function store($key, $resource)
{
if ($this->isStorageEnabled()) {
$this->storage[$key] = $resource;
}
return $resource;
}
/**
* Checks if local storage is enabled
*
* @return bool returns true if local storage is enabled false otherwise
*/
public function isStorageEnabled()
{
return (bool)$this->isStorageEnabled;
}
/**
* Enable local storage
*/
public function enableStorage()
{
$this->isStorageEnabled = true;
}
/**
* Toggle local storage
*/
public function toggleStorage()
{
$this->isStorageEnabled = (bool)!$this->isStorageEnabled;
}
/**
* Disable local storage
*/
public function disableStorage()
{
$this->isStorageEnabled = false;
}
/**
* Check if race condition protection is enabled
*
* @return bool returns true if protection is enabled
*/
public function isProtected()
{
return (bool)$this->isProtected;
}
/**
* Enable race condition protection
*/
public function enableProtection()
{
$this->isProtected = true;
}
/**
* Toggle race condition protection
*/
public function toggleProtection()
{
$this->isProtected = (bool)!$this->isProtected;
}
/**
* Disable race condition protection
*/
public function disableProtection()
{
$this->isProtected = false;
}
/**
* Check if data compression is enabled
*
* @return bool returns true if data compression is enabled
*/
public function isCompressed()
{
return (bool)$this->instance()->getOption(Memcached::OPT_COMPRESSION);
}
/**
* Enable data compression
*/
public function enableCompression()
{
$this->instance()->setOption(Memcached::OPT_COMPRESSION, true);
}
/**
* Toggle data compression
*/
public function toggleCompression()
{
$this->instance()->setOption(
Memcached::OPT_COMPRESSION,
(bool)!$this->instance()->getOption(Memcached::OPT_COMPRESSION)
);
}
/**
* Disable data compression
*/
public function disableCompression()
{
$this->instance()->setOption(Memcached::OPT_COMPRESSION, false);
}
/**
* Toggle all of the custom options
*/
public function toggleAll()
{
$this->toggleStorage();
$this->toggleCompression();
$this->toggleProtection();
}
/**
* Deactivate caching
*/
public function deactivate()
{
$this->isActive = false;
}
/**
* Activate caching
*/
public function activate()
{
$this->isActive = true;
}
/**
* Check if caching is currently active
*
* @return bool returns true if caching is active otherwise false
*/
public function isActive()
{
return (bool)$this->isActive;
}
/**
* Validate array of cache servers that should be loaded to Memcached extension
*
* @throws Exception if we detect something out of specification
* @param mixed[] $servers a list of Memcached servers we will be using
*/
public function validateServers(array $servers)
{
if ($servers) {
foreach ($servers as $key => $server) {
// Check number of parameters associated with passed server
if (!is_array($server) || count($server) < 2 || count($server) > 3) {
throw new Exception(
'Invalid server parameters found in passed server array on key ' . $key . ', please see'
. ' http://www.php.net/manual/en/memcached.addservers.php'
);
}
// Auto adjust weight of servers
if (count($server) == 2) {
$server[] = 100;
}
list($ip, $port, $weight) = $server;
// Check port and weight
if (!is_numeric($port) || (!is_null($weight) && !is_numeric($weight))) {
throw new Exception(
'Invalid server port and/or weight found in passed server array on key ' . $key . ', please see'
. ' http://www.php.net/manual/en/memcached.addservers.php'
);
}
}
}
}
/**
* Retrieve instance of Memcached client
*
* @return Memcached
*/
public function instance()
{
return $this->memcached;
}
/**
* Pass all method calls directly to instance of Memcached
*
* @param string $name method that was invoked
* @param mixed[] $arguments arguments that were passed to invoked method
* @return mixed
*/
public function __call($name, $arguments)
{
// Methods we currently do not support
$blacklist = array(
'getMultiByKey',
'replaceByKey',
'setByKey',
'setMulti',
'setMultiByKey'
);
if (in_array($name, $blacklist)) {
throw new Exception(
'Requested method is currently not supported'
);
}
// Methods that should not be protected/compressed/covered by local storage
$unprotected = array(
'prependByKey',
'appendByKey',
'append',
'prepend',
'increment',
'decrement',
'decrementByKey',
'incrementByKey',
);
if (in_array($name, $unprotected)) {
if ($this->isProtected()) {
throw new Exception(
'Please turn off race condition protection when using this method.'
);
}
if ($this->isStorageEnabled()) {
throw new Exception(
'Please turn off storage when using this method.'
);
}
if ($this->isCompressed()) {
throw new Exception(
'Please turn off compression when using this method.'
);
}
}
return call_user_func_array(
array(
$this->instance(),
$name
),
$arguments
);
}
}
Function Calls
None |
Stats
MD5 | 131f62ee6c1c2e06a2edf58f46d5cd25 |
Eval Count | 0 |
Decode Time | 136 ms |