diff --git a/.travis.yml b/.travis.yml index c66457325f..e2aa84f5c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,11 @@ before_script: - cp config/local-sample.config.php config/local.config.php - mysql -e 'CREATE DATABASE IF NOT EXISTS test;' - mysql -utravis test < database.sql - - echo "extension=redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini - - echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + - pecl channel-update pecl.php.net + - pecl config-set preferred_state beta + - if [[ $TRAVIS_PHP_VERSION != "7.1" ]]; then echo yes | pecl upgrade apcu; fi + - if [[ $TRAVIS_PHP_VERSION != "7.1" ]]; then phpenv config-add .travis/apcu.ini; fi + - phpenv config-add .travis/redis.ini + - phpenv config-add .travis/memcached.ini + after_success: bash <(curl -s https://codecov.io/bash) diff --git a/.travis/apcu.ini b/.travis/apcu.ini new file mode 100644 index 0000000000..92598662cf --- /dev/null +++ b/.travis/apcu.ini @@ -0,0 +1,4 @@ +extension="apcu.so" + +apc.enabled = 1 +apc.enable_cli = 1 \ No newline at end of file diff --git a/.travis/memcached.ini b/.travis/memcached.ini new file mode 100644 index 0000000000..c9a2ff0c9a --- /dev/null +++ b/.travis/memcached.ini @@ -0,0 +1 @@ +extension="memcached.so" \ No newline at end of file diff --git a/.travis/redis.ini b/.travis/redis.ini new file mode 100644 index 0000000000..ab995b8374 --- /dev/null +++ b/.travis/redis.ini @@ -0,0 +1 @@ +extension="redis.so" \ No newline at end of file diff --git a/src/Core/Cache/APCuCache.php b/src/Core/Cache/APCuCache.php new file mode 100644 index 0000000000..f658424cdc --- /dev/null +++ b/src/Core/Cache/APCuCache.php @@ -0,0 +1,154 @@ + + */ +class APCuCache extends AbstractCacheDriver implements IMemoryCacheDriver +{ + use TraitCompareSet; + use TraitCompareDelete; + + /** + * @throws Exception + */ + public function __construct() + { + if (!self::isAvailable()) { + throw new Exception('APCu is not available.'); + } + } + + /** + * (@inheritdoc) + */ + public function getAllKeys($prefix = null) + { + $ns = $this->getCacheKey($prefix); + $ns = preg_quote($ns, '/'); + + if (class_exists('\APCIterator')) { + $iterator = new \APCIterator('user', '/^' . $ns. '/', APC_ITER_KEY); + } else { + $iterator = new \APCUIterator('/^' . $ns . '/', APC_ITER_KEY); + } + + $keys = []; + foreach ($iterator as $item) { + array_push($keys, $item['key']); + } + + return $this->getOriginalKeys($keys); + } + + /** + * (@inheritdoc) + */ + public function get($key) + { + $return = null; + $cachekey = $this->getCacheKey($key); + + $cached = apcu_fetch($cachekey, $success); + if (!$success) { + return null; + } + + $value = unserialize($cached); + + // Only return a value if the serialized value is valid. + // We also check if the db entry is a serialized + // boolean 'false' value (which we want to return). + if ($cached === serialize(false) || $value !== false) { + $return = $value; + } + + return $return; + } + + /** + * (@inheritdoc) + */ + public function set($key, $value, $ttl = Cache::FIVE_MINUTES) + { + $cachekey = $this->getCacheKey($key); + + $cached = serialize($value); + + if ($ttl > 0) { + return apcu_store( + $cachekey, + $cached, + $ttl + ); + } else { + return apcu_store( + $cachekey, + $cached + ); + } + } + + /** + * (@inheritdoc) + */ + public function delete($key) + { + $cachekey = $this->getCacheKey($key); + return apcu_delete($cachekey); + } + + /** + * (@inheritdoc) + */ + public function clear($outdated = true) + { + if ($outdated) { + return true; + } else { + $prefix = $this->getPrefix(); + $prefix = preg_quote($prefix, '/'); + + if (class_exists('\APCIterator')) { + $iterator = new \APCIterator('user', '/^' . $prefix . '/', APC_ITER_KEY); + } else { + $iterator = new \APCUIterator('/^' . $prefix . '/', APC_ITER_KEY); + } + + return apcu_delete($iterator); + } + } + + /** + * (@inheritdoc) + */ + public function add($key, $value, $ttl = Cache::FIVE_MINUTES) + { + $cachekey = $this->getCacheKey($key); + $cached = serialize($value); + + return apcu_add($cachekey, $cached); + } + + public static function isAvailable() + { + if (!extension_loaded('apcu')) { + return false; + } elseif (!ini_get('apc.enabled') && !ini_get('apc.enable_cli')) { + return false; + } elseif ( + version_compare(phpversion('apc') ?: '0.0.0', '4.0.6') === -1 && + version_compare(phpversion('apcu') ?: '0.0.0', '5.1.0') === -1 + ) { + return false; + } + + return true; + } +} diff --git a/src/Core/Cache/AbstractCacheDriver.php b/src/Core/Cache/AbstractCacheDriver.php index 13993385ad..f238a7819c 100644 --- a/src/Core/Cache/AbstractCacheDriver.php +++ b/src/Core/Cache/AbstractCacheDriver.php @@ -13,6 +13,18 @@ use Friendica\BaseObject; */ abstract class AbstractCacheDriver extends BaseObject { + /** + * Returns the prefix (to avoid namespace conflicts) + * + * @return string + * @throws \Exception + */ + protected function getPrefix() + { + // We fetch with the hostname as key to avoid problems with other applications + return self::getApp()->getHostName(); + } + /** * @param string $key The original key * @return string The cache key used for the cache @@ -20,8 +32,7 @@ abstract class AbstractCacheDriver extends BaseObject */ protected function getCacheKey($key) { - // We fetch with the hostname as key to avoid problems with other applications - return self::getApp()->getHostName() . ":" . $key; + return $this->getPrefix() . ":" . $key; } /** diff --git a/src/Factory/CacheDriverFactory.php b/src/Factory/CacheDriverFactory.php index f802d73fa6..390534a70a 100644 --- a/src/Factory/CacheDriverFactory.php +++ b/src/Factory/CacheDriverFactory.php @@ -45,6 +45,11 @@ class CacheDriverFactory return new Cache\RedisCacheDriver($redis_host, $redis_port, $redis_db, $redis_pw); break; + + case 'apcu': + return new Cache\APCuCache(); + break; + default: return new Cache\DatabaseCacheDriver(); } diff --git a/tests/src/Core/Cache/APCuCacheDriverTest.php b/tests/src/Core/Cache/APCuCacheDriverTest.php new file mode 100644 index 0000000000..eac00f559a --- /dev/null +++ b/tests/src/Core/Cache/APCuCacheDriverTest.php @@ -0,0 +1,29 @@ +markTestSkipped('APCu is not available'); + } + + parent::setUp(); + } + + protected function getInstance() + { + $this->cache = new APCuCache(); + return $this->cache; + } + + public function tearDown() + { + $this->cache->clear(false); + parent::tearDown(); + } +} diff --git a/tests/src/Core/Lock/APCuCacheLockDriverTest.php b/tests/src/Core/Lock/APCuCacheLockDriverTest.php new file mode 100644 index 0000000000..6e185d6b9a --- /dev/null +++ b/tests/src/Core/Lock/APCuCacheLockDriverTest.php @@ -0,0 +1,24 @@ +markTestSkipped('APCu is not available'); + } + + parent::setUp(); + } + + protected function getInstance() + { + return new CacheLockDriver(new APCuCache()); + } +}