Files
newznab-tmux/Blacklight/libraries/Cache.php
T
2018-02-14 23:08:17 +01:00

391 lines
12 KiB
PHP
Executable File

<?php
namespace Blacklight\libraries;
/**
* Class Cache.
*
* Class for connecting to a memcached or redis server to cache data.
*/
class Cache
{
private const SERIALIZER_PHP = 0;
private const SERIALIZER_IGBINARY = 1;
private const SERIALIZER_NONE = 2;
private const TYPE_DISABLED = 0;
private const TYPE_MEMCACHED = 1;
private const TYPE_REDIS = 2;
private const TYPE_APC = 3;
/**
* @var \Memcached
*/
private $server;
/**
* Are we connected to the cache server?
* @var bool
*/
private $connected = false;
/**
* Optional socket file location.
* @var bool|string
*/
private $socketFile;
/**
* Store data on the cache server.
*
*
* @param $key
* @param $data
* @param $expiration
* @return bool
* @throws \Blacklight\libraries\CacheException
*/
public function set($key, $data, $expiration): bool
{
if ($this->ping()) {
switch (NN_CACHE_TYPE) {
case self::TYPE_REDIS:
case self::TYPE_MEMCACHED:
return $this->server->set($key, $data, $expiration);
case self::TYPE_APC:
return apc_add($key, $data, $expiration);
}
}
return false;
}
/**
* Attempt to retrieve a value from the cache server, if not set it.
*
*
* @param $key
* @return bool|mixed|string
* @throws \Blacklight\libraries\CacheException
*/
public function get($key)
{
if ($this->ping()) {
$data = '';
switch (NN_CACHE_TYPE) {
case self::TYPE_REDIS:
case self::TYPE_MEMCACHED:
$data = $this->server->get($key);
break;
case self::TYPE_APC:
$data = apc_fetch($key);
break;
}
return $data;
}
return false;
}
/**
* Delete data tied to a key on the cache server.
*
*
* @param $key
* @return bool|string[]
* @throws \Blacklight\libraries\CacheException
*/
public function delete($key)
{
if ($this->ping()) {
switch (NN_CACHE_TYPE) {
case self::TYPE_REDIS:
case self::TYPE_MEMCACHED:
return (bool) $this->server->delete($key);
case self::TYPE_APC:
return apc_delete($key);
}
}
return false;
}
/**
* Flush all data from the cache server?
*
*
* @throws \Blacklight\libraries\CacheException
*/
public function flush()
{
if ($this->ping()) {
switch (NN_CACHE_TYPE) {
case self::TYPE_REDIS:
$this->server->flushAll();
break;
case self::TYPE_MEMCACHED:
$this->server->flush();
break;
case self::TYPE_APC:
apc_clear_cache('user');
apc_clear_cache();
break;
}
}
}
/**
* Create a SHA1 hash from a string which can be used to store/retrieve data.
*
* @param string $string
*
* @return string SHA1 hash of the input string.
*/
public function createKey($string)
{
return sha1($string);
}
/**
* Get cache server statistics.
*
*
* @return array|bool
* @throws \Blacklight\libraries\CacheException
*/
public function serverStatistics()
{
if ($this->ping()) {
switch (NN_CACHE_TYPE) {
case self::TYPE_REDIS:
return $this->server->info();
case self::TYPE_MEMCACHED:
return $this->server->getStats();
case self::TYPE_APC:
return apc_cache_info();
}
}
return [];
}
/**
* Verify the user's cache settings, try to connect to the cache server.
*
* @throws \Blacklight\libraries\CacheException
*/
public function __construct()
{
if (! \defined('NN_CACHE_HOSTS')) {
throw new CacheException(
'The NN_CACHE_HOSTS is not defined! Define it in settings.php'
);
}
if (! \defined('NN_CACHE_TIMEOUT')) {
throw new CacheException(
'The NN_CACHE_TIMEOUT is not defined! Define it in settings.php, it is the time in seconds to time out from your cache server.'
);
}
$this->socketFile = false;
if (\defined('NN_CACHE_SOCKET_FILE') && NN_CACHE_SOCKET_FILE != '') {
$this->socketFile = true;
}
$serializer = false;
if (\defined('NN_CACHE_SERIALIZER')) {
$serializer = true;
}
switch (NN_CACHE_TYPE) {
case self::TYPE_REDIS:
if (! \extension_loaded('redis')) {
throw new CacheException('The redis extension is not loaded!');
}
$this->server = new \Redis();
$this->connect();
if ($serializer) {
$this->server->setOption(\Redis::OPT_SERIALIZER, $this->verifySerializer());
}
break;
case self::TYPE_MEMCACHED:
if (! \extension_loaded('memcached')) {
throw new CacheException('The memcached extension is not loaded!');
}
$this->server = new \Memcached();
if ($serializer) {
$this->server->setOption(\Memcached::OPT_SERIALIZER, $this->verifySerializer());
}
$this->server->setOption(\Memcached::OPT_COMPRESSION, (defined('NN_CACHE_COMPRESSION') ? NN_CACHE_COMPRESSION : false));
$this->connect();
break;
case self::TYPE_APC:
// Faster than checking if apcu or apc is loaded.
if (! \function_exists('apc_add')) {
throw new CacheException('The APCu extension is not loaded or enabled!');
}
$this->connect();
break;
case self::TYPE_DISABLED:
default:
break;
}
}
/**
* Destroy the connections.
*/
public function __destruct()
{
switch (NN_CACHE_TYPE) {
case self::TYPE_REDIS:
$this->server->close();
break;
case self::TYPE_MEMCACHED:
$this->server->quit();
break;
}
}
/**
* Connect to the cache server(s).
*
* @throws CacheException
*/
private function connect()
{
$this->connected = false;
switch (NN_CACHE_TYPE) {
case self::TYPE_REDIS:
if ($this->socketFile === false) {
$servers = unserialize(NN_CACHE_HOSTS);
foreach ($servers as $server) {
if ($this->server->connect($server['host'], $server['port'], (float) NN_CACHE_TIMEOUT) === false) {
throw new CacheException('Error connecting to the Redis server!');
} else {
$this->connected = true;
}
}
} else {
if ($this->server->connect(NN_CACHE_SOCKET_FILE) === false) {
throw new CacheException('Error connecting to the Redis server!');
} else {
$this->connected = true;
}
}
break;
case self::TYPE_MEMCACHED:
$params = ($this->socketFile === false ? unserialize(NN_CACHE_HOSTS) : [[NN_CACHE_SOCKET_FILE, 'port' => 0]]);
if ($this->server->addServers($params) === false) {
throw new CacheException('Error connecting to the Memcached server!');
} else {
$this->connected = true;
}
break;
case self::TYPE_APC:
$this->connected = true;
break;
}
}
/**
* Check if we are still connected to the cache server, reconnect if not.
*
*
* @return bool
* @throws \Blacklight\libraries\CacheException
*/
private function ping()
{
if (! $this->connected) {
return false;
}
switch (NN_CACHE_TYPE) {
case self::TYPE_REDIS:
try {
return (bool) $this->server->ping();
} catch (\RedisException $error) {
// nothing to see here, move along
}
break;
case self::TYPE_MEMCACHED:
$versions = $this->server->getVersion();
if ($versions) {
foreach ($versions as $version) {
if ($version != '255.255.255') {
return true;
}
}
}
break;
case self::TYPE_APC:
return true;
default:
return false;
}
$this->connect();
return $this->connected;
}
/**
* Verify the user selected serializer, return the memcached or redis appropriate serializer option.
*
* @return int
* @throws CacheException
*/
private function verifySerializer()
{
switch (NN_CACHE_SERIALIZER) {
case self::SERIALIZER_IGBINARY:
if (! \extension_loaded('igbinary')) {
throw new CacheException('Error: The igbinary extension is not loaded!');
}
switch (NN_CACHE_TYPE) {
case self::TYPE_REDIS:
// If this is not defined, it means phpredis was not compiled with --enable-redis-igbinary
if (! \defined('\Redis::SERIALIZER_IGBINARY')) {
throw new CacheException('Error: phpredis was not compiled with igbinary support!');
}
return \Redis::SERIALIZER_IGBINARY;
case self::TYPE_MEMCACHED:
if (\Memcached::HAVE_IGBINARY > 0) {
return \Memcached::SERIALIZER_IGBINARY;
}
throw new CacheException('Error: You have not compiled Memcached with igbinary support!');
case self::TYPE_APC: // Ignore - set by apc.serializer setting.
default:
return null;
}
// no break
case self::SERIALIZER_NONE:
// Only redis supports this.
if (NN_CACHE_TYPE !== self::TYPE_REDIS) {
throw new CacheException('Error: Disabled serialization is only available on Redis!');
}
return \Redis::SERIALIZER_NONE;
case self::SERIALIZER_PHP:
default:
switch (NN_CACHE_TYPE) {
case self::TYPE_REDIS:
return \Redis::SERIALIZER_PHP;
case self::TYPE_MEMCACHED:
return \Memcached::SERIALIZER_PHP;
default:
return null;
}
}
}
}