diff --git a/.travis.yml b/.travis.yml index b20b73ca7..6b4b96eb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,7 @@ services: env: global: - TESTS_ZEND_CACHE_APC_ENABLED: true + - TESTS_ZEND_CACHE_APCU_ENABLED=true - TESTS_ZEND_CACHE_MEMCACHED_ENABLED: true - TESTS_ZEND_CACHE_MEMCACHED_HOST: "127.0.0.1" - TESTS_ZEND_CACHE_MEMCACHED_PORT: 11211 @@ -72,6 +73,7 @@ matrix: - INSTALL_XCACHE=true - php: 7 env: + - EXECUTE_TEST_COVERALLS=true - PECL_INSTALL_APCU='apcu' - PECL_INSTALL_APCU_BC='apcu_bc-beta' - php: 7 diff --git a/CHANGELOG.md b/CHANGELOG.md index 58a705f25..30fc9ea6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ All notable changes to this project will be documented in this file, in reverse ### Added +- [#23](https://github.com/zendframework/zend-cache/issues/23) + [#47](https://github.com/zendframework/zend-cache/issues/47) + Added an Apcu storage adapter as future replacement for Apc - [#63](https://github.com/zendframework/zend-cache/pull/63) Implemented ClearByNamespaceInterface in Stoage\Adapter\Redis diff --git a/composer.json b/composer.json index b13c9a7ab..722e67f34 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,8 @@ "suggest": { "zendframework/zend-serializer": "Zend\\Serializer component", "zendframework/zend-session": "Zend\\Session component", - "ext-apcu": "APCU, to use the APC storage adapter", + "ext-apc": "APC or compatible extension, to use the APC storage adapter", + "ext-apcu": "APCU >= 5.1.0, to use the APCu storage adapter", "ext-dba": "DBA, to use the DBA storage adapter", "ext-memcache": "Memcache >= 2.0.0 to use the Memcache storage adapter", "ext-memcached": "Memcached >= 1.0.0 to use the Memcached storage adapter", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4ca04d8ff..8e443e2b6 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -35,6 +35,7 @@ constants appropriate to the component you are migrating. --> + diff --git a/src/Storage/Adapter/Apcu.php b/src/Storage/Adapter/Apcu.php new file mode 100644 index 000000000..fa40d89cb --- /dev/null +++ b/src/Storage/Adapter/Apcu.php @@ -0,0 +1,740 @@ += 5.1.0'); + } + + if (!ini_get('apc.enabled') || (PHP_SAPI === 'cli' && !ini_get('apc.enable_cli'))) { + throw new Exception\ExtensionNotLoadedException( + "ext/apcu is disabled - see 'apc.enabled' and 'apc.enable_cli'" + ); + } + + parent::__construct($options); + } + + /* options */ + + /** + * Set options. + * + * @param array|Traversable|ApcuOptions $options + * @return Apcu + * @see getOptions() + */ + public function setOptions($options) + { + if (!$options instanceof ApcuOptions) { + $options = new ApcuOptions($options); + } + + return parent::setOptions($options); + } + + /** + * Get options. + * + * @return ApcuOptions + * @see setOptions() + */ + public function getOptions() + { + if (!$this->options) { + $this->setOptions(new ApcuOptions()); + } + return $this->options; + } + + /* TotalSpaceCapableInterface */ + + /** + * Get total space in bytes + * + * @return int|float + */ + public function getTotalSpace() + { + if ($this->totalSpace === null) { + $smaInfo = apcu_sma_info(true); + $this->totalSpace = $smaInfo['num_seg'] * $smaInfo['seg_size']; + } + + return $this->totalSpace; + } + + /* AvailableSpaceCapableInterface */ + + /** + * Get available space in bytes + * + * @return int|float + */ + public function getAvailableSpace() + { + $smaInfo = apcu_sma_info(true); + return $smaInfo['avail_mem']; + } + + /* IterableInterface */ + + /** + * Get the storage iterator + * + * @return ApcuIterator + */ + public function getIterator() + { + $options = $this->getOptions(); + $namespace = $options->getNamespace(); + $prefix = ''; + $pattern = null; + if ($namespace !== '') { + $prefix = $namespace . $options->getNamespaceSeparator(); + $pattern = '/^' . preg_quote($prefix, '/') . '/'; + } + + $baseIt = new BaseApcuIterator($pattern, 0, 1, APC_LIST_ACTIVE); + return new ApcuIterator($this, $baseIt, $prefix); + } + + /* FlushableInterface */ + + /** + * Flush the whole storage + * + * @return bool + */ + public function flush() + { + return apcu_clear_cache(); + } + + /* ClearByNamespaceInterface */ + + /** + * Remove items by given namespace + * + * @param string $namespace + * @return bool + */ + public function clearByNamespace($namespace) + { + $namespace = (string) $namespace; + if ($namespace === '') { + throw new Exception\InvalidArgumentException('No namespace given'); + } + + $options = $this->getOptions(); + $prefix = $namespace . $options->getNamespaceSeparator(); + $pattern = '/^' . preg_quote($prefix, '/') . '/'; + return apcu_delete(new BaseApcuIterator($pattern, 0, 1, APC_LIST_ACTIVE)); + } + + /* ClearByPrefixInterface */ + + /** + * Remove items matching given prefix + * + * @param string $prefix + * @return bool + */ + public function clearByPrefix($prefix) + { + $prefix = (string) $prefix; + if ($prefix === '') { + throw new Exception\InvalidArgumentException('No prefix given'); + } + + $options = $this->getOptions(); + $namespace = $options->getNamespace(); + $nsPrefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator(); + $pattern = '/^' . preg_quote($nsPrefix . $prefix, '/') . '/'; + return apcu_delete(new BaseApcuIterator($pattern, 0, 1, APC_LIST_ACTIVE)); + } + + /* reading */ + + /** + * Internal method to get an item. + * + * @param string $normalizedKey + * @param bool $success + * @param mixed $casToken + * @return mixed Data on success, null on failure + * @throws Exception\ExceptionInterface + */ + protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null) + { + $options = $this->getOptions(); + $namespace = $options->getNamespace(); + $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator(); + $internalKey = $prefix . $normalizedKey; + $result = apcu_fetch($internalKey, $success); + + if (!$success) { + return; + } + + $casToken = $result; + return $result; + } + + /** + * Internal method to get multiple items. + * + * @param array $normalizedKeys + * @return array Associative array of keys and values + * @throws Exception\ExceptionInterface + */ + protected function internalGetItems(array & $normalizedKeys) + { + $options = $this->getOptions(); + $namespace = $options->getNamespace(); + if ($namespace === '') { + return apcu_fetch($normalizedKeys); + } + + $prefix = $namespace . $options->getNamespaceSeparator(); + $internalKeys = []; + foreach ($normalizedKeys as $normalizedKey) { + $internalKeys[] = $prefix . $normalizedKey; + } + + $fetch = apcu_fetch($internalKeys); + + // remove namespace prefix + $prefixL = strlen($prefix); + $result = []; + foreach ($fetch as $internalKey => $value) { + $result[substr($internalKey, $prefixL)] = $value; + } + + return $result; + } + + /** + * Internal method to test if an item exists. + * + * @param string $normalizedKey + * @return bool + * @throws Exception\ExceptionInterface + */ + protected function internalHasItem(& $normalizedKey) + { + $options = $this->getOptions(); + $namespace = $options->getNamespace(); + $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator(); + return apcu_exists($prefix . $normalizedKey); + } + + /** + * Internal method to test multiple items. + * + * @param array $normalizedKeys + * @return array Array of found keys + * @throws Exception\ExceptionInterface + */ + protected function internalHasItems(array & $normalizedKeys) + { + $options = $this->getOptions(); + $namespace = $options->getNamespace(); + if ($namespace === '') { + // array_filter with no callback will remove entries equal to FALSE + return array_keys(array_filter(apcu_exists($normalizedKeys))); + } + + $prefix = $namespace . $options->getNamespaceSeparator(); + $internalKeys = []; + foreach ($normalizedKeys as $normalizedKey) { + $internalKeys[] = $prefix . $normalizedKey; + } + + $exists = apcu_exists($internalKeys); + $result = []; + $prefixL = strlen($prefix); + foreach ($exists as $internalKey => $bool) { + if ($bool === true) { + $result[] = substr($internalKey, $prefixL); + } + } + + return $result; + } + + /** + * Get metadata of an item. + * + * @param string $normalizedKey + * @return array|bool Metadata on success, false on failure + * @throws Exception\ExceptionInterface + */ + protected function internalGetMetadata(& $normalizedKey) + { + $options = $this->getOptions(); + $namespace = $options->getNamespace(); + $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator(); + $internalKey = $prefix . $normalizedKey; + + $format = APC_ITER_ALL ^ APC_ITER_VALUE ^ APC_ITER_TYPE ^ APC_ITER_REFCOUNT; + $regexp = '/^' . preg_quote($internalKey, '/') . '$/'; + $it = new BaseApcuIterator($regexp, $format, 100, APC_LIST_ACTIVE); + $metadata = $it->current(); + + if (!$metadata) { + return false; + } + + $this->normalizeMetadata($metadata); + return $metadata; + } + + /** + * Get metadata of multiple items + * + * @param array $normalizedKeys + * @return array Associative array of keys and metadata + * + * @triggers getMetadatas.pre(PreEvent) + * @triggers getMetadatas.post(PostEvent) + * @triggers getMetadatas.exception(ExceptionEvent) + */ + protected function internalGetMetadatas(array & $normalizedKeys) + { + $keysRegExp = []; + foreach ($normalizedKeys as $normalizedKey) { + $keysRegExp[] = preg_quote($normalizedKey, '/'); + } + + $options = $this->getOptions(); + $namespace = $options->getNamespace(); + $prefixL = 0; + + if ($namespace === '') { + $pattern = '/^(' . implode('|', $keysRegExp) . ')' . '$/'; + } else { + $prefix = $namespace . $options->getNamespaceSeparator(); + $prefixL = strlen($prefix); + $pattern = '/^' . preg_quote($prefix, '/') . '(' . implode('|', $keysRegExp) . ')' . '$/'; + } + + $format = APC_ITER_ALL ^ APC_ITER_VALUE ^ APC_ITER_TYPE ^ APC_ITER_REFCOUNT; + $it = new BaseApcuIterator($pattern, $format, 100, APC_LIST_ACTIVE); + $result = []; + foreach ($it as $internalKey => $metadata) { + $this->normalizeMetadata($metadata); + $result[substr($internalKey, $prefixL)] = $metadata; + } + + return $result; + } + + /* writing */ + + /** + * Internal method to store an item. + * + * @param string $normalizedKey + * @param mixed $value + * @return bool + * @throws Exception\ExceptionInterface + */ + protected function internalSetItem(& $normalizedKey, & $value) + { + $options = $this->getOptions(); + $namespace = $options->getNamespace(); + $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator(); + $internalKey = $prefix . $normalizedKey; + $ttl = $options->getTtl(); + + if (!apcu_store($internalKey, $value, $ttl)) { + $type = is_object($value) ? get_class($value) : gettype($value); + throw new Exception\RuntimeException( + "apcu_store('{$internalKey}', <{$type}>, {$ttl}) failed" + ); + } + + return true; + } + + /** + * Internal method to store multiple items. + * + * @param array $normalizedKeyValuePairs + * @return array Array of not stored keys + * @throws Exception\ExceptionInterface + */ + protected function internalSetItems(array & $normalizedKeyValuePairs) + { + $options = $this->getOptions(); + $namespace = $options->getNamespace(); + if ($namespace === '') { + return array_keys(apcu_store($normalizedKeyValuePairs, null, $options->getTtl())); + } + + $prefix = $namespace . $options->getNamespaceSeparator(); + $internalKeyValuePairs = []; + foreach ($normalizedKeyValuePairs as $normalizedKey => $value) { + $internalKey = $prefix . $normalizedKey; + $internalKeyValuePairs[$internalKey] = $value; + } + + $failedKeys = apcu_store($internalKeyValuePairs, null, $options->getTtl()); + $failedKeys = array_keys($failedKeys); + + // remove prefix + $prefixL = strlen($prefix); + foreach ($failedKeys as $key) { + $key = substr($key, $prefixL); + } + + return $failedKeys; + } + + /** + * Add an item. + * + * @param string $normalizedKey + * @param mixed $value + * @return bool + * @throws Exception\ExceptionInterface + */ + protected function internalAddItem(& $normalizedKey, & $value) + { + $options = $this->getOptions(); + $namespace = $options->getNamespace(); + $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator(); + $internalKey = $prefix . $normalizedKey; + $ttl = $options->getTtl(); + + if (!apcu_add($internalKey, $value, $ttl)) { + if (apcu_exists($internalKey)) { + return false; + } + + $type = is_object($value) ? get_class($value) : gettype($value); + throw new Exception\RuntimeException( + "apcu_add('{$internalKey}', <{$type}>, {$ttl}) failed" + ); + } + + return true; + } + + /** + * Internal method to add multiple items. + * + * @param array $normalizedKeyValuePairs + * @return array Array of not stored keys + * @throws Exception\ExceptionInterface + */ + protected function internalAddItems(array & $normalizedKeyValuePairs) + { + $options = $this->getOptions(); + $namespace = $options->getNamespace(); + if ($namespace === '') { + return array_keys(apcu_add($normalizedKeyValuePairs, null, $options->getTtl())); + } + + $prefix = $namespace . $options->getNamespaceSeparator(); + $internalKeyValuePairs = []; + foreach ($normalizedKeyValuePairs as $normalizedKey => $value) { + $internalKey = $prefix . $normalizedKey; + $internalKeyValuePairs[$internalKey] = $value; + } + + $failedKeys = apcu_add($internalKeyValuePairs, null, $options->getTtl()); + $failedKeys = array_keys($failedKeys); + + // remove prefix + $prefixL = strlen($prefix); + foreach ($failedKeys as & $key) { + $key = substr($key, $prefixL); + } + + return $failedKeys; + } + + /** + * Internal method to replace an existing item. + * + * @param string $normalizedKey + * @param mixed $value + * @return bool + * @throws Exception\ExceptionInterface + */ + protected function internalReplaceItem(& $normalizedKey, & $value) + { + $options = $this->getOptions(); + $ttl = $options->getTtl(); + $namespace = $options->getNamespace(); + $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator(); + $internalKey = $prefix . $normalizedKey; + + + if (!apcu_exists($internalKey)) { + return false; + } + + if (!apcu_store($internalKey, $value, $ttl)) { + $type = is_object($value) ? get_class($value) : gettype($value); + throw new Exception\RuntimeException( + "apcu_store('{$internalKey}', <{$type}>, {$ttl}) failed" + ); + } + + return true; + } + + /** + * Internal method to set an item only if token matches + * + * @param mixed $token + * @param string $normalizedKey + * @param mixed $value + * @return bool + * @see getItem() + * @see setItem() + */ + protected function internalCheckAndSetItem(& $token, & $normalizedKey, & $value) + { + if (is_int($token) && is_int($value)) { + return apcu_cas($normalizedKey, $token, $value); + } + + return parent::internalCheckAndSetItem($token, $normalizedKey, $value); + } + + /** + * Internal method to remove an item. + * + * @param string $normalizedKey + * @return bool + * @throws Exception\ExceptionInterface + */ + protected function internalRemoveItem(& $normalizedKey) + { + $options = $this->getOptions(); + $namespace = $options->getNamespace(); + $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator(); + return apcu_delete($prefix . $normalizedKey); + } + + /** + * Internal method to remove multiple items. + * + * @param array $normalizedKeys + * @return array Array of not removed keys + * @throws Exception\ExceptionInterface + */ + protected function internalRemoveItems(array & $normalizedKeys) + { + $options = $this->getOptions(); + $namespace = $options->getNamespace(); + if ($namespace === '') { + return apcu_delete($normalizedKeys); + } + + $prefix = $namespace . $options->getNamespaceSeparator(); + $internalKeys = []; + foreach ($normalizedKeys as $normalizedKey) { + $internalKeys[] = $prefix . $normalizedKey; + } + + $failedKeys = apcu_delete($internalKeys); + + // remove prefix + $prefixL = strlen($prefix); + foreach ($failedKeys as & $key) { + $key = substr($key, $prefixL); + } + + return $failedKeys; + } + + /** + * Internal method to increment an item. + * + * @param string $normalizedKey + * @param int $value + * @return int|bool The new value on success, false on failure + * @throws Exception\ExceptionInterface + */ + protected function internalIncrementItem(& $normalizedKey, & $value) + { + $options = $this->getOptions(); + $namespace = $options->getNamespace(); + $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator(); + $internalKey = $prefix . $normalizedKey; + $value = (int) $value; + $newValue = apcu_inc($internalKey, $value); + + // initial value + if ($newValue === false) { + $ttl = $options->getTtl(); + $newValue = $value; + if (!apcu_add($internalKey, $newValue, $ttl)) { + throw new Exception\RuntimeException( + "apcu_add('{$internalKey}', {$newValue}, {$ttl}) failed" + ); + } + } + + return $newValue; + } + + /** + * Internal method to decrement an item. + * + * @param string $normalizedKey + * @param int $value + * @return int|bool The new value on success, false on failure + * @throws Exception\ExceptionInterface + */ + protected function internalDecrementItem(& $normalizedKey, & $value) + { + $options = $this->getOptions(); + $namespace = $options->getNamespace(); + $prefix = ($namespace === '') ? '' : $namespace . $options->getNamespaceSeparator(); + $internalKey = $prefix . $normalizedKey; + $value = (int) $value; + $newValue = apcu_dec($internalKey, $value); + + // initial value + if ($newValue === false) { + $ttl = $options->getTtl(); + $newValue = -$value; + if (!apcu_add($internalKey, $newValue, $ttl)) { + throw new Exception\RuntimeException( + "apcu_add('{$internalKey}', {$newValue}, {$ttl}) failed" + ); + } + } + + return $newValue; + } + + /* status */ + + /** + * Internal method to get capabilities of this adapter + * + * @return Capabilities + */ + protected function internalGetCapabilities() + { + if ($this->capabilities === null) { + $marker = new stdClass(); + $capabilities = new Capabilities( + $this, + $marker, + [ + 'supportedDatatypes' => [ + 'NULL' => true, + 'boolean' => true, + 'integer' => true, + 'double' => true, + 'string' => true, + 'array' => true, + 'object' => 'object', + 'resource' => false, + ], + 'supportedMetadata' => [ + 'internal_key', + 'atime', 'ctime', 'mtime', 'rtime', + 'size', 'hits', 'ttl', + ], + 'minTtl' => 1, + 'maxTtl' => 0, + 'staticTtl' => true, + 'ttlPrecision' => 1, + 'useRequestTime' => (bool) ini_get('apc.use_request_time'), + 'expiredRead' => false, + 'maxKeyLength' => 5182, + 'namespaceIsPrefix' => true, + 'namespaceSeparator' => $this->getOptions()->getNamespaceSeparator(), + ] + ); + + // update namespace separator on change option + $this->getEventManager()->attach('option', function ($event) use ($capabilities, $marker) { + $params = $event->getParams(); + + if (isset($params['namespace_separator'])) { + $capabilities->setNamespaceSeparator($marker, $params['namespace_separator']); + } + }); + + $this->capabilities = $capabilities; + $this->capabilityMarker = $marker; + } + + return $this->capabilities; + } + + /* internal */ + + /** + * Normalize metadata to work with APC + * + * @param array $metadata + * @return void + */ + protected function normalizeMetadata(array & $metadata) + { + $apcMetadata = $metadata; + $metadata = [ + 'internal_key' => $metadata['key'], + 'atime' => $metadata['access_time'], + 'ctime' => $metadata['creation_time'], + 'mtime' => $metadata['mtime'], + 'rtime' => $metadata['deletion_time'], + 'size' => $metadata['mem_size'], + 'hits' => $metadata['num_hits'], + 'ttl' => $metadata['ttl'], + ]; + } +} diff --git a/src/Storage/Adapter/ApcuIterator.php b/src/Storage/Adapter/ApcuIterator.php new file mode 100644 index 000000000..3b397ec07 --- /dev/null +++ b/src/Storage/Adapter/ApcuIterator.php @@ -0,0 +1,157 @@ +storage = $storage; + $this->baseIterator = $baseIterator; + $this->prefixLength = strlen($prefix); + } + + /** + * Get storage instance + * + * @return Apcu + */ + public function getStorage() + { + return $this->storage; + } + + /** + * Get iterator mode + * + * @return int Value of IteratorInterface::CURRENT_AS_* + */ + public function getMode() + { + return $this->mode; + } + + /** + * Set iterator mode + * + * @param int $mode + * @return ApcuIterator Fluent interface + */ + public function setMode($mode) + { + $this->mode = (int) $mode; + return $this; + } + + /* Iterator */ + + /** + * Get current key, value or metadata. + * + * @return mixed + */ + public function current() + { + if ($this->mode == IteratorInterface::CURRENT_AS_SELF) { + return $this; + } + + $key = $this->key(); + + if ($this->mode == IteratorInterface::CURRENT_AS_VALUE) { + return $this->storage->getItem($key); + } elseif ($this->mode == IteratorInterface::CURRENT_AS_METADATA) { + return $this->storage->getMetadata($key); + } + + return $key; + } + + /** + * Get current key + * + * @return string + */ + public function key() + { + $key = $this->baseIterator->key(); + + // remove namespace prefix + return substr($key, $this->prefixLength); + } + + /** + * Move forward to next element + * + * @return void + */ + public function next() + { + $this->baseIterator->next(); + } + + /** + * Checks if current position is valid + * + * @return bool + */ + public function valid() + { + return $this->baseIterator->valid(); + } + + /** + * Rewind the Iterator to the first element. + * + * @return void + */ + public function rewind() + { + return $this->baseIterator->rewind(); + } +} diff --git a/src/Storage/Adapter/ApcuOptions.php b/src/Storage/Adapter/ApcuOptions.php new file mode 100644 index 000000000..b4297dddb --- /dev/null +++ b/src/Storage/Adapter/ApcuOptions.php @@ -0,0 +1,47 @@ +triggerOptionEvent('namespace_separator', $namespaceSeparator); + $this->namespaceSeparator = $namespaceSeparator; + return $this; + } + + /** + * Get namespace separator + * + * @return string + */ + public function getNamespaceSeparator() + { + return $this->namespaceSeparator; + } +} diff --git a/src/Storage/AdapterPluginManager.php b/src/Storage/AdapterPluginManager.php index bddc515e9..3f3b907a2 100644 --- a/src/Storage/AdapterPluginManager.php +++ b/src/Storage/AdapterPluginManager.php @@ -26,6 +26,10 @@ class AdapterPluginManager extends AbstractPluginManager protected $aliases = [ 'apc' => Adapter\Apc::class, 'Apc' => Adapter\Apc::class, + 'apcu' => Adapter\Apcu::class, + 'ApcU' => Adapter\Apcu::class, + 'Apcu' => Adapter\Apcu::class, + 'APCu' => Adapter\Apcu::class, 'blackhole' => Adapter\BlackHole::class, 'blackHole' => Adapter\BlackHole::class, 'BlackHole' => Adapter\BlackHole::class, @@ -62,6 +66,7 @@ class AdapterPluginManager extends AbstractPluginManager protected $factories = [ Adapter\Apc::class => InvokableFactory::class, + Adapter\Apcu::class => InvokableFactory::class, Adapter\BlackHole::class => InvokableFactory::class, Adapter\Dba::class => InvokableFactory::class, Adapter\Filesystem::class => InvokableFactory::class, @@ -78,6 +83,7 @@ class AdapterPluginManager extends AbstractPluginManager // v2 normalized FQCNs 'zendcachestorageadapterapc' => InvokableFactory::class, + 'zendcachestorageadapterapcu' => InvokableFactory::class, 'zendcachestorageadapterblackhole' => InvokableFactory::class, 'zendcachestorageadapterdba' => InvokableFactory::class, 'zendcachestorageadapterfilesystem' => InvokableFactory::class, diff --git a/test/Storage/Adapter/ApcuTest.php b/test/Storage/Adapter/ApcuTest.php new file mode 100644 index 000000000..e37c3a0ae --- /dev/null +++ b/test/Storage/Adapter/ApcuTest.php @@ -0,0 +1,71 @@ + + */ +class ApcuTest extends CommonAdapterTest +{ + /** + * Restore 'apc.use_request_time' + * + * @var mixed + */ + protected $iniUseRequestTime; + + public function setUp() + { + if (!getenv('TESTS_ZEND_CACHE_APCU_ENABLED')) { + $this->markTestSkipped('Enable TESTS_ZEND_CACHE_APCU_ENABLED to run this test'); + } + + $enabled = extension_loaded('apcu') && version_compare(phpversion('apcu'), '5.1.0', '>='); + $enabled = $enabled && ini_get('apc.enabled') && (PHP_SAPI !== 'cli' || ini_get('apc.enable_cli')); + + try { + $apcu = new Cache\Storage\Adapter\Apcu(); + if (!$enabled) { + $this->fail('Missing expected ExtensionNotLoadedException'); + } + + // needed on test expirations + $this->iniUseRequestTime = ini_get('apc.use_request_time'); + ini_set('apc.use_request_time', 0); + + $this->_options = new Cache\Storage\Adapter\ApcuOptions(); + $this->_storage = $apcu; + $this->_storage->setOptions($this->_options); + } catch (Cache\Exception\ExtensionNotLoadedException $e) { + if ($enabled) { + $this->fail('ext/apcu enabled but an ExtensionNotLoadedException was thrown: ' . $e->getMessage()); + } else { + $this->markTestSkipped($e->getMessage()); + } + } + + parent::setUp(); + } + + public function tearDown() + { + if (function_exists('apcu_clear_cache')) { + apcu_clear_cache(); + } + + // reset ini configurations + ini_set('apc.use_request_time', $this->iniUseRequestTime); + + parent::tearDown(); + } +}