Support for APCu caching
authorPhilipp Holzer <admin@philipp.info>
Sat, 20 Apr 2019 15:37:57 +0000 (17:37 +0200)
committerPhilipp Holzer <admin@philipp.info>
Sun, 21 Apr 2019 10:45:19 +0000 (12:45 +0200)
.travis.yml
src/Core/Cache/APCuCache.php [new file with mode: 0644]
src/Core/Cache/AbstractCacheDriver.php
src/Factory/CacheDriverFactory.php
tests/src/Core/Cache/APCuCacheDriverTest.php [new file with mode: 0644]
tests/src/Core/Lock/APCuCacheLockDriverTest.php [new file with mode: 0644]

index c664573..a241dff 100644 (file)
@@ -21,4 +21,5 @@ before_script:
  - 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
+ - echo "apc.enable_cli = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
 after_success: bash <(curl -s https://codecov.io/bash)
diff --git a/src/Core/Cache/APCuCache.php b/src/Core/Cache/APCuCache.php
new file mode 100644 (file)
index 0000000..f658424
--- /dev/null
@@ -0,0 +1,154 @@
+<?php
+
+namespace Friendica\Core\Cache;
+
+use Exception;
+use Friendica\Core\Cache;
+
+/**
+ * APCu Cache Driver.
+ *
+ * @author Philipp Holzer <admin@philipp.info>
+ */
+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;
+       }
+}
index 1399338..ee67eb7 100644 (file)
@@ -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;
        }
 
        /**
index f802d73..390534a 100644 (file)
@@ -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 (file)
index 0000000..eac00f5
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+namespace Friendica\Test\src\Core\Cache;
+
+use Friendica\Core\Cache\APCuCache;
+
+class APCuCacheDriverTest extends MemoryCacheTest
+{
+       protected function setUp()
+       {
+               if (!APCuCache::isAvailable()) {
+                       $this->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 (file)
index 0000000..7b0137e
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+
+namespace Friendica\Test\src\Core\Lock;
+
+
+use Friendica\Core\Cache\APCuCache;
+use Friendica\Core\Lock\CacheLockDriver;
+
+class APCuCacheLockDriverTest extends LockTest
+{
+       protected function getInstance()
+       {
+               return new CacheLockDriver(new APCuCache());
+       }
+}