3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
10 namespace Zend\Cache\Storage\Adapter
;
12 use APCIterator
as BaseApcIterator
;
15 use Zend\Cache\Exception
;
16 use Zend\Cache\Storage\AvailableSpaceCapableInterface
;
17 use Zend\Cache\Storage\Capabilities
;
18 use Zend\Cache\Storage\ClearByNamespaceInterface
;
19 use Zend\Cache\Storage\ClearByPrefixInterface
;
20 use Zend\Cache\Storage\FlushableInterface
;
21 use Zend\Cache\Storage\IterableInterface
;
22 use Zend\Cache\Storage\TotalSpaceCapableInterface
;
24 class Apc
extends AbstractAdapter
implements
25 AvailableSpaceCapableInterface
,
26 ClearByNamespaceInterface
,
27 ClearByPrefixInterface
,
30 TotalSpaceCapableInterface
33 * Buffered total space in bytes
37 protected $totalSpace;
42 * @param null|array|Traversable|ApcOptions $options
43 * @throws Exception\ExceptionInterface
45 public function __construct($options = null)
47 if (version_compare('3.1.6', phpversion('apc')) > 0) {
48 throw new Exception\
ExtensionNotLoadedException("Missing ext/apc >= 3.1.6");
51 $enabled = ini_get('apc.enabled');
52 if (PHP_SAPI
== 'cli') {
53 $enabled = $enabled && (bool) ini_get('apc.enable_cli');
57 throw new Exception\
ExtensionNotLoadedException(
58 "ext/apc is disabled - see 'apc.enabled' and 'apc.enable_cli'"
62 parent
::__construct($options);
70 * @param array|Traversable|ApcOptions $options
74 public function setOptions($options)
76 if (!$options instanceof ApcOptions
) {
77 $options = new ApcOptions($options);
80 return parent
::setOptions($options);
89 public function getOptions()
91 if (!$this->options
) {
92 $this->setOptions(new ApcOptions());
94 return $this->options
;
97 /* TotalSpaceCapableInterface */
100 * Get total space in bytes
104 public function getTotalSpace()
106 if ($this->totalSpace
=== null) {
107 $smaInfo = apc_sma_info(true);
108 $this->totalSpace
= $smaInfo['num_seg'] * $smaInfo['seg_size'];
111 return $this->totalSpace
;
114 /* AvailableSpaceCapableInterface */
117 * Get available space in bytes
121 public function getAvailableSpace()
123 $smaInfo = apc_sma_info(true);
124 return $smaInfo['avail_mem'];
127 /* IterableInterface */
130 * Get the storage iterator
132 * @return ApcIterator
134 public function getIterator()
136 $options = $this->getOptions();
137 $namespace = $options->getNamespace();
140 if ($namespace !== '') {
141 $prefix = $namespace . $options->getNamespaceSeparator();
142 $pattern = '/^' . preg_quote($prefix, '/') . '/';
145 $baseIt = new BaseApcIterator('user', $pattern, 0, 1, APC_LIST_ACTIVE
);
146 return new ApcIterator($this, $baseIt, $prefix);
149 /* FlushableInterface */
152 * Flush the whole storage
156 public function flush()
158 return apc_clear_cache('user');
161 /* ClearByNamespaceInterface */
164 * Remove items by given namespace
166 * @param string $namespace
169 public function clearByNamespace($namespace)
171 $namespace = (string) $namespace;
172 if ($namespace === '') {
173 throw new Exception\
InvalidArgumentException('No namespace given');
176 $options = $this->getOptions();
177 $prefix = $namespace . $options->getNamespaceSeparator();
178 $pattern = '/^' . preg_quote($prefix, '/') . '/';
179 return apc_delete(new BaseApcIterator('user', $pattern, 0, 1, APC_LIST_ACTIVE
));
182 /* ClearByPrefixInterface */
185 * Remove items matching given prefix
187 * @param string $prefix
190 public function clearByPrefix($prefix)
192 $prefix = (string) $prefix;
193 if ($prefix === '') {
194 throw new Exception\
InvalidArgumentException('No prefix given');
197 $options = $this->getOptions();
198 $namespace = $options->getNamespace();
199 $nsPrefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator();
200 $pattern = '/^' . preg_quote($nsPrefix . $prefix, '/') . '/';
201 return apc_delete(new BaseApcIterator('user', $pattern, 0, 1, APC_LIST_ACTIVE
));
207 * Internal method to get an item.
209 * @param string $normalizedKey
210 * @param bool $success
211 * @param mixed $casToken
212 * @return mixed Data on success, null on failure
213 * @throws Exception\ExceptionInterface
215 protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null)
217 $options = $this->getOptions();
218 $namespace = $options->getNamespace();
219 $prefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator();
220 $internalKey = $prefix . $normalizedKey;
221 $result = apc_fetch($internalKey, $success);
232 * Internal method to get multiple items.
234 * @param array $normalizedKeys
235 * @return array Associative array of keys and values
236 * @throws Exception\ExceptionInterface
238 protected function internalGetItems(array & $normalizedKeys)
240 $options = $this->getOptions();
241 $namespace = $options->getNamespace();
242 if ($namespace === '') {
243 return apc_fetch($normalizedKeys);
246 $prefix = $namespace . $options->getNamespaceSeparator();
247 $internalKeys = array();
248 foreach ($normalizedKeys as $normalizedKey) {
249 $internalKeys[] = $prefix . $normalizedKey;
252 $fetch = apc_fetch($internalKeys);
254 // remove namespace prefix
255 $prefixL = strlen($prefix);
257 foreach ($fetch as $internalKey => & $value) {
258 $result[substr($internalKey, $prefixL)] = $value;
265 * Internal method to test if an item exists.
267 * @param string $normalizedKey
269 * @throws Exception\ExceptionInterface
271 protected function internalHasItem(& $normalizedKey)
273 $options = $this->getOptions();
274 $namespace = $options->getNamespace();
275 $prefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator();
276 return apc_exists($prefix . $normalizedKey);
280 * Internal method to test multiple items.
282 * @param array $normalizedKeys
283 * @return array Array of found keys
284 * @throws Exception\ExceptionInterface
286 protected function internalHasItems(array & $normalizedKeys)
288 $options = $this->getOptions();
289 $namespace = $options->getNamespace();
290 if ($namespace === '') {
291 // array_filter with no callback will remove entries equal to FALSE
292 return array_keys(array_filter(apc_exists($normalizedKeys)));
295 $prefix = $namespace . $options->getNamespaceSeparator();
296 $internalKeys = array();
297 foreach ($normalizedKeys as $normalizedKey) {
298 $internalKeys[] = $prefix . $normalizedKey;
301 $exists = apc_exists($internalKeys);
303 $prefixL = strlen($prefix);
304 foreach ($exists as $internalKey => $bool) {
305 if ($bool === true) {
306 $result[] = substr($internalKey, $prefixL);
314 * Get metadata of an item.
316 * @param string $normalizedKey
317 * @return array|bool Metadata on success, false on failure
318 * @throws Exception\ExceptionInterface
320 protected function internalGetMetadata(& $normalizedKey)
322 $options = $this->getOptions();
323 $namespace = $options->getNamespace();
324 $prefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator();
325 $internalKey = $prefix . $normalizedKey;
327 // @see http://pecl.php.net/bugs/bug.php?id=22564
328 if (!apc_exists($internalKey)) {
331 $format = APC_ITER_ALL ^ APC_ITER_VALUE ^ APC_ITER_TYPE ^ APC_ITER_REFCOUNT
;
332 $regexp = '/^' . preg_quote($internalKey, '/') . '$/';
333 $it = new BaseApcIterator('user', $regexp, $format, 100, APC_LIST_ACTIVE
);
334 $metadata = $it->current();
341 $this->normalizeMetadata($metadata);
346 * Get metadata of multiple items
348 * @param array $normalizedKeys
349 * @return array Associative array of keys and metadata
351 * @triggers getMetadatas.pre(PreEvent)
352 * @triggers getMetadatas.post(PostEvent)
353 * @triggers getMetadatas.exception(ExceptionEvent)
355 protected function internalGetMetadatas(array & $normalizedKeys)
357 $keysRegExp = array();
358 foreach ($normalizedKeys as $normalizedKey) {
359 $keysRegExp[] = preg_quote($normalizedKey, '/');
362 $options = $this->getOptions();
363 $namespace = $options->getNamespace();
364 if ($namespace === '') {
365 $pattern = '/^(' . implode('|', $keysRegExp) . ')' . '$/';
367 $prefix = $namespace . $options->getNamespaceSeparator();
368 $pattern = '/^' . preg_quote($prefix, '/') . '(' . implode('|', $keysRegExp) . ')' . '$/';
370 $format = APC_ITER_ALL ^ APC_ITER_VALUE ^ APC_ITER_TYPE ^ APC_ITER_REFCOUNT
;
371 $it = new BaseApcIterator('user', $pattern, $format, 100, APC_LIST_ACTIVE
);
373 $prefixL = strlen($prefix);
374 foreach ($it as $internalKey => $metadata) {
375 // @see http://pecl.php.net/bugs/bug.php?id=22564
376 if (!apc_exists($internalKey)) {
380 $this->normalizeMetadata($metadata);
381 $result[substr($internalKey, $prefixL)] = & $metadata;
390 * Internal method to store an item.
392 * @param string $normalizedKey
393 * @param mixed $value
395 * @throws Exception\ExceptionInterface
397 protected function internalSetItem(& $normalizedKey, & $value)
399 $options = $this->getOptions();
400 $namespace = $options->getNamespace();
401 $prefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator();
402 $internalKey = $prefix . $normalizedKey;
403 $ttl = $options->getTtl();
405 if (!apc_store($internalKey, $value, $ttl)) {
406 $type = is_object($value) ?
get_class($value) : gettype($value);
407 throw new Exception\
RuntimeException(
408 "apc_store('{$internalKey}', <{$type}>, {$ttl}) failed"
416 * Internal method to store multiple items.
418 * @param array $normalizedKeyValuePairs
419 * @return array Array of not stored keys
420 * @throws Exception\ExceptionInterface
422 protected function internalSetItems(array & $normalizedKeyValuePairs)
424 $options = $this->getOptions();
425 $namespace = $options->getNamespace();
426 if ($namespace === '') {
427 return array_keys(apc_store($normalizedKeyValuePairs, null, $options->getTtl()));
430 $prefix = $namespace . $options->getNamespaceSeparator();
431 $internalKeyValuePairs = array();
432 foreach ($normalizedKeyValuePairs as $normalizedKey => &$value) {
433 $internalKey = $prefix . $normalizedKey;
434 $internalKeyValuePairs[$internalKey] = &$value;
437 $failedKeys = apc_store($internalKeyValuePairs, null, $options->getTtl());
438 $failedKeys = array_keys($failedKeys);
441 $prefixL = strlen($prefix);
442 foreach ($failedKeys as & $key) {
443 $key = substr($key, $prefixL);
452 * @param string $normalizedKey
453 * @param mixed $value
455 * @throws Exception\ExceptionInterface
457 protected function internalAddItem(& $normalizedKey, & $value)
459 $options = $this->getOptions();
460 $namespace = $options->getNamespace();
461 $prefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator();
462 $internalKey = $prefix . $normalizedKey;
463 $ttl = $options->getTtl();
465 if (!apc_add($internalKey, $value, $ttl)) {
466 if (apc_exists($internalKey)) {
470 $type = is_object($value) ?
get_class($value) : gettype($value);
471 throw new Exception\
RuntimeException(
472 "apc_add('{$internalKey}', <{$type}>, {$ttl}) failed"
480 * Internal method to add multiple items.
482 * @param array $normalizedKeyValuePairs
483 * @return array Array of not stored keys
484 * @throws Exception\ExceptionInterface
486 protected function internalAddItems(array & $normalizedKeyValuePairs)
488 $options = $this->getOptions();
489 $namespace = $options->getNamespace();
490 if ($namespace === '') {
491 return array_keys(apc_add($normalizedKeyValuePairs, null, $options->getTtl()));
494 $prefix = $namespace . $options->getNamespaceSeparator();
495 $internalKeyValuePairs = array();
496 foreach ($normalizedKeyValuePairs as $normalizedKey => $value) {
497 $internalKey = $prefix . $normalizedKey;
498 $internalKeyValuePairs[$internalKey] = $value;
501 $failedKeys = apc_add($internalKeyValuePairs, null, $options->getTtl());
502 $failedKeys = array_keys($failedKeys);
505 $prefixL = strlen($prefix);
506 foreach ($failedKeys as & $key) {
507 $key = substr($key, $prefixL);
514 * Internal method to replace an existing item.
516 * @param string $normalizedKey
517 * @param mixed $value
519 * @throws Exception\ExceptionInterface
521 protected function internalReplaceItem(& $normalizedKey, & $value)
523 $options = $this->getOptions();
524 $namespace = $options->getNamespace();
525 $prefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator();
526 $internalKey = $prefix . $normalizedKey;
528 if (!apc_exists($internalKey)) {
532 $ttl = $options->getTtl();
533 if (!apc_store($internalKey, $value, $ttl)) {
534 $type = is_object($value) ?
get_class($value) : gettype($value);
535 throw new Exception\
RuntimeException(
536 "apc_store('{$internalKey}', <{$type}>, {$ttl}) failed"
544 * Internal method to remove an item.
546 * @param string $normalizedKey
548 * @throws Exception\ExceptionInterface
550 protected function internalRemoveItem(& $normalizedKey)
552 $options = $this->getOptions();
553 $namespace = $options->getNamespace();
554 $prefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator();
555 return apc_delete($prefix . $normalizedKey);
559 * Internal method to remove multiple items.
561 * @param array $normalizedKeys
562 * @return array Array of not removed keys
563 * @throws Exception\ExceptionInterface
565 protected function internalRemoveItems(array & $normalizedKeys)
567 $options = $this->getOptions();
568 $namespace = $options->getNamespace();
569 if ($namespace === '') {
570 return apc_delete($normalizedKeys);
573 $prefix = $namespace . $options->getNamespaceSeparator();
574 $internalKeys = array();
575 foreach ($normalizedKeys as $normalizedKey) {
576 $internalKeys[] = $prefix . $normalizedKey;
579 $failedKeys = apc_delete($internalKeys);
582 $prefixL = strlen($prefix);
583 foreach ($failedKeys as & $key) {
584 $key = substr($key, $prefixL);
591 * Internal method to increment an item.
593 * @param string $normalizedKey
595 * @return int|bool The new value on success, false on failure
596 * @throws Exception\ExceptionInterface
598 protected function internalIncrementItem(& $normalizedKey, & $value)
600 $options = $this->getOptions();
601 $namespace = $options->getNamespace();
602 $prefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator();
603 $internalKey = $prefix . $normalizedKey;
604 $ttl = $options->getTtl();
605 $value = (int) $value;
606 $newValue = apc_inc($internalKey, $value);
609 if ($newValue === false) {
610 $ttl = $options->getTtl();
612 if (!apc_add($internalKey, $newValue, $ttl)) {
613 throw new Exception\
RuntimeException(
614 "apc_add('{$internalKey}', {$newValue}, {$ttl}) failed"
623 * Internal method to decrement an item.
625 * @param string $normalizedKey
627 * @return int|bool The new value on success, false on failure
628 * @throws Exception\ExceptionInterface
630 protected function internalDecrementItem(& $normalizedKey, & $value)
632 $options = $this->getOptions();
633 $namespace = $options->getNamespace();
634 $prefix = ($namespace === '') ?
'' : $namespace . $options->getNamespaceSeparator();
635 $internalKey = $prefix . $normalizedKey;
636 $value = (int) $value;
637 $newValue = apc_dec($internalKey, $value);
640 if ($newValue === false) {
641 $ttl = $options->getTtl();
643 if (!apc_add($internalKey, $newValue, $ttl)) {
644 throw new Exception\
RuntimeException(
645 "apc_add('{$internalKey}', {$newValue}, {$ttl}) failed"
656 * Internal method to get capabilities of this adapter
658 * @return Capabilities
660 protected function internalGetCapabilities()
662 if ($this->capabilities
=== null) {
663 $marker = new stdClass();
664 $capabilities = new Capabilities(
668 'supportedDatatypes' => array(
675 'object' => 'object',
678 'supportedMetadata' => array(
680 'atime', 'ctime', 'mtime', 'rtime',
681 'size', 'hits', 'ttl',
687 'useRequestTime' => (bool) ini_get('apc.use_request_time'),
688 'expiredRead' => false,
689 'maxKeyLength' => 5182,
690 'namespaceIsPrefix' => true,
691 'namespaceSeparator' => $this->getOptions()->getNamespaceSeparator(),
695 // update namespace separator on change option
696 $this->getEventManager()->attach('option', function ($event) use ($capabilities, $marker) {
697 $params = $event->getParams();
699 if (isset($params['namespace_separator'])) {
700 $capabilities->setNamespaceSeparator($marker, $params['namespace_separator']);
704 $this->capabilities
= $capabilities;
705 $this->capabilityMarker
= $marker;
708 return $this->capabilities
;
714 * Normalize metadata to work with APC
716 * @param array $metadata
719 protected function normalizeMetadata(array & $metadata)
721 $metadata['internal_key'] = $metadata['key'];
722 $metadata['ctime'] = $metadata['creation_time'];
723 $metadata['atime'] = $metadata['access_time'];
724 $metadata['rtime'] = $metadata['deletion_time'];
725 $metadata['size'] = $metadata['mem_size'];
726 $metadata['hits'] = $metadata['num_hits'];
730 $metadata['creation_time'],
731 $metadata['access_time'],
732 $metadata['deletion_time'],
733 $metadata['mem_size'],
734 $metadata['num_hits']