Skip to content

Instantly share code, notes, and snippets.

@nickelfault
Last active January 7, 2017 04:52
Show Gist options
  • Save nickelfault/12200664d2aa56315dddc27eeb90c9bd to your computer and use it in GitHub Desktop.
Save nickelfault/12200664d2aa56315dddc27eeb90c9bd to your computer and use it in GitHub Desktop.
Memcache Engine for CakePHP 3.x (src/Cache/Engine/MemcacheEngine.php)
<?php
/**
* Memcache storage engine for cache
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @package Cake.Cache.Engine
* @since CakePHP(tm) v 1.2.0.4933
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace App\Cache\Engine;
use Cake\Cache\CacheEngine;
use Cake\Utility\Inflector;
use InvalidArgumentException;
/**
* Memcache storage engine for cache. Memcache has some limitations in the amount of
* control you have over expire times far in the future. See MemcacheEngine::write() for
* more information.
*
* @package Cake.Cache.Engine
* @deprecated 3.0.0 You should use the Memcached adapter instead.
*/
class MemcacheEngine extends CacheEngine {
/**
* Contains the compiled group names
* (prefixed with the global configuration prefix)
*
* @var array
*/
protected $_compiledGroupNames = [];
/**
* Memcache wrapper.
*
* @var Memcache
*/
protected $_Memcache = null;
/**
* The default config used unless overridden by runtime configuration
*
* - `compress` Whether to compress data
* - `duration` Specify how long items in this cache configuration last.
* - `groups` List of groups or 'tags' associated to every key stored in this config.
* handy for deleting a complete group from cache.
* - `host` Hostname or IP address of memcached server
* - `persistent` Whether or not to use a persistent connection
* - `port` Port of the memcached server(s)
* - `prefix` Prepended to all entries. Good for when you need to share a keyspace
* with either another cache config or another application.
* - `probability` Probability of hitting a cache gc cleanup. Setting to 0 will disable
* cache::gc from ever being called automatically.
* - `servers` String or array of memcached servers. If an array MemcacheEngine will use
* them as a pool.
*
* @var array
*/
protected $_defaultConfig = [
'compress' => false,
'duration' => 3600,
'groups' => [],
'host' => null,
'persistent' => true,
'port' => 11211,
'prefix' => 'cake_',
'probability' => 100,
'servers' => ['127.0.0.1'],
];
/**
* Initialize the Cache Engine
*
* Called automatically by the cache frontend
* To reinitialize the settings call Cache::engine('EngineName', [optional] settings = array());
*
* @param array $config array of setting for the engine
* @return bool True if the engine has been successfully initialized, false if not
*/
public function init(array $config = []) {
if (!class_exists('Memcache')) {
return false;
}
if (!isset($config['prefix'])) {
$config['prefix'] = Inflector::slug(APP_DIR) . '_';
}
parent::init($config);
if ($this->_config['compress']) {
$this->_config['compress'] = MEMCACHE_COMPRESSED;
}
if (!empty($config['host'])) {
if (empty($config['port'])) {
$config['servers'] = [$config['host']];
} else {
$config['servers'] = [sprintf('%s:%d', $config['host'], $config['port'])];
}
}
if (isset($config['servers'])) {
$this->config('servers', $config['servers'], false);
}
if (!is_array($this->_config['servers'])) {
$this->_config['servers'] = [$this->_config['servers']];
}
if (!isset($this->_Memcache)) {
$return = false;
$this->_Memcache = new \Memcache();
foreach ($this->_config['servers'] as $server) {
list($host, $port) = $this->_parseServerString($server);
if ($this->_Memcache->addServer($host, $port, $this->_config['persistent'])) {
$return = true;
}
}
return $return;
}
return true;
}
/**
* Parses the server address into the host/port. Handles both IPv6 and IPv4
* addresses and Unix sockets
*
* @param string $server The server address string.
* @return array Array containing host, port
*/
protected function _parseServerString($server) {
if ($server[0] === 'u') {
return [
$server,
0,
];
}
if (substr($server, 0, 1) === '[') {
$position = strpos($server, ']:');
if ($position !== false) {
$position++;
}
} else {
$position = strpos($server, ':');
}
$port = $this->_config['port'];
$host = $server;
if ($position !== false) {
$host = substr($server, 0, $position);
$port = substr($server, $position + 1);
}
return [
$host,
$port,
];
}
/**
* Write data for key into cache. When using memcache as your cache engine
* remember that the Memcache pecl extension does not support cache expiry times greater
* than 30 days in the future. Any duration greater than 30 days will be treated as never expiring.
*
* @param string $key Identifier for the data
* @param mixed $value Data to be cached
* @return bool True if the data was successfully cached, false on failure
* @see http://php.net/manual/en/memcache.set.php
*/
public function write($key, $value) {
$duration = $this->_config['duration'];
if ($duration > 30 * DAY) {
$duration = 0;
}
return $this->_Memcache->set($key, $value, $this->_config['compress'], $duration);
}
/**
* Read a key from the cache
*
* @param string $key Identifier for the data
* @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
*/
public function read($key) {
return $this->_Memcache->get($key);
}
/**
* Increments the value of an integer cached key
*
* @param string $key Identifier for the data
* @param int $offset How much to increment
* @return New incremented value, false otherwise
* @throws InvalidArgumentException when you try to increment with compress = true
*/
public function increment($key, $offset = 1) {
if ($this->_config['compress']) {
throw new InvalidArgumentException(sprintf('Method %s not implemented for compressed cache in %s', 'increment()', __CLASS__));
}
return $this->_Memcache->increment($key, $offset);
}
/**
* Decrements the value of an integer cached key
*
* @param string $key Identifier for the data
* @param int $offset How much to subtract
* @return New decremented value, false otherwise
* @throws InvalidArgumentException when you try to decrement with compress = true
*/
public function decrement($key, $offset = 1) {
if ($this->_config['compress']) {
throw new InvalidArgumentException(sprintf('Method %s not implemented for compressed cache in %s', 'decrement()', __CLASS__));
}
return $this->_Memcache->decrement($key, $offset);
}
/**
* Delete a key from the cache
*
* @param string $key Identifier for the data
* @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed
*/
public function delete($key) {
return $this->_Memcache->delete($key);
}
/**
* Delete all keys from the cache
*
* @param bool $check If true no deletes will occur and instead CakePHP will rely
* on key TTL values.
* @return bool True if the cache was successfully cleared, false otherwise
*/
public function clear($check) {
if ($check) {
return true;
}
foreach ($this->_Memcache->getExtendedStats('slabs', 0) as $slabs) {
foreach (array_keys($slabs) as $slabId) {
if (!is_numeric($slabId)) {
continue;
}
foreach ($this->_Memcache->getExtendedStats('cachedump', $slabId, 0) as $stats) {
if (!is_array($stats)) {
continue;
}
foreach (array_keys($stats) as $key) {
if (strpos($key, $this->_config['prefix']) === 0) {
$this->_Memcache->delete($key);
}
}
}
}
}
return true;
}
/**
* Connects to a server in connection pool
*
* @param string $host host ip address or name
* @return bool True if memcache server was connected
*/
public function connect($host) {
if ($this->_Memcache->getServerStatus($host, $this->_config['port']) === 0) {
if ($this->_Memcache->connect($host, $this->_config['port'])) {
return true;
}
return false;
}
return true;
}
/**
* Returns the `group value` for each of the configured groups
* If the group initial value was not found, then it initializes
* the group accordingly.
*
* @return array
*/
public function groups() {
if (empty($this->_compiledGroupNames)) {
foreach ($this->_config['groups'] as $group) {
$this->_compiledGroupNames[] = $this->_config['prefix'] . $group;
}
}
$groups = $this->_Memcache->get($this->_compiledGroupNames);
if (count($groups) !== count($this->_config['groups'])) {
foreach ($this->_compiledGroupNames as $group) {
if (!isset($groups[$group])) {
$this->_Memcache->set($group, 1, false, 0);
$groups[$group] = 1;
}
}
ksort($groups);
}
$result = [];
$groups = array_values($groups);
foreach ($this->_config['groups'] as $i => $group) {
$result[] = $group . $groups[$i];
}
return $result;
}
/**
* Increments the group value to simulate deletion of all keys under a group
* old values will remain in storage until they expire.
*
* @param string $group The group to clear.
* @return bool success
*/
public function clearGroup($group) {
return (bool)$this->_Memcache->increment($this->_config['prefix'] . $group);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment