1<?php 2/** 3 * Zend Framework (http://framework.zend.com/) 4 * 5 * @link http://github.com/zendframework/zf2 for the canonical source repository 6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 7 * @license http://framework.zend.com/license/new-bsd New BSD License 8 */ 9 10namespace Zend\Cache\Storage\Adapter; 11 12use ArrayObject; 13use SplObjectStorage; 14use stdClass; 15use Traversable; 16use Zend\Cache\Exception; 17use Zend\Cache\Storage\Capabilities; 18use Zend\Cache\Storage\Event; 19use Zend\Cache\Storage\ExceptionEvent; 20use Zend\Cache\Storage\Plugin; 21use Zend\Cache\Storage\PostEvent; 22use Zend\Cache\Storage\StorageInterface; 23use Zend\EventManager\EventManager; 24use Zend\EventManager\EventManagerInterface; 25use Zend\EventManager\EventsCapableInterface; 26 27abstract class AbstractAdapter implements StorageInterface, EventsCapableInterface 28{ 29 /** 30 * The used EventManager if any 31 * 32 * @var null|EventManagerInterface 33 */ 34 protected $events = null; 35 36 /** 37 * Event handles of this adapter 38 * @var array 39 */ 40 protected $eventHandles = array(); 41 42 /** 43 * The plugin registry 44 * 45 * @var SplObjectStorage Registered plugins 46 */ 47 protected $pluginRegistry; 48 49 /** 50 * Capabilities of this adapter 51 * 52 * @var null|Capabilities 53 */ 54 protected $capabilities = null; 55 56 /** 57 * Marker to change capabilities 58 * 59 * @var null|object 60 */ 61 protected $capabilityMarker; 62 63 /** 64 * options 65 * 66 * @var mixed 67 */ 68 protected $options; 69 70 /** 71 * Constructor 72 * 73 * @param null|array|Traversable|AdapterOptions $options 74 * @throws Exception\ExceptionInterface 75 */ 76 public function __construct($options = null) 77 { 78 if ($options) { 79 $this->setOptions($options); 80 } 81 } 82 83 /** 84 * Destructor 85 * 86 * detach all registered plugins to free 87 * event handles of event manager 88 * 89 * @return void 90 */ 91 public function __destruct() 92 { 93 foreach ($this->getPluginRegistry() as $plugin) { 94 $this->removePlugin($plugin); 95 } 96 97 if ($this->eventHandles) { 98 $events = $this->getEventManager(); 99 foreach ($this->eventHandles as $handle) { 100 $events->detach($handle); 101 } 102 } 103 } 104 105 /* configuration */ 106 107 /** 108 * Set options. 109 * 110 * @param array|Traversable|AdapterOptions $options 111 * @return AbstractAdapter 112 * @see getOptions() 113 */ 114 public function setOptions($options) 115 { 116 if ($this->options !== $options) { 117 if (!$options instanceof AdapterOptions) { 118 $options = new AdapterOptions($options); 119 } 120 121 if ($this->options) { 122 $this->options->setAdapter(null); 123 } 124 $options->setAdapter($this); 125 $this->options = $options; 126 127 $event = new Event('option', $this, new ArrayObject($options->toArray())); 128 $this->getEventManager()->trigger($event); 129 } 130 return $this; 131 } 132 133 /** 134 * Get options. 135 * 136 * @return AdapterOptions 137 * @see setOptions() 138 */ 139 public function getOptions() 140 { 141 if (!$this->options) { 142 $this->setOptions(new AdapterOptions()); 143 } 144 return $this->options; 145 } 146 147 /** 148 * Enable/Disable caching. 149 * 150 * Alias of setWritable and setReadable. 151 * 152 * @see setWritable() 153 * @see setReadable() 154 * @param bool $flag 155 * @return AbstractAdapter 156 */ 157 public function setCaching($flag) 158 { 159 $flag = (bool) $flag; 160 $options = $this->getOptions(); 161 $options->setWritable($flag); 162 $options->setReadable($flag); 163 return $this; 164 } 165 166 /** 167 * Get caching enabled. 168 * 169 * Alias of getWritable and getReadable. 170 * 171 * @see getWritable() 172 * @see getReadable() 173 * @return bool 174 */ 175 public function getCaching() 176 { 177 $options = $this->getOptions(); 178 return ($options->getWritable() && $options->getReadable()); 179 } 180 181 /* Event/Plugin handling */ 182 183 /** 184 * Get the event manager 185 * 186 * @return EventManagerInterface 187 */ 188 public function getEventManager() 189 { 190 if ($this->events === null) { 191 $this->events = new EventManager(array(__CLASS__, get_class($this))); 192 } 193 return $this->events; 194 } 195 196 /** 197 * Trigger a pre event and return the event response collection 198 * 199 * @param string $eventName 200 * @param ArrayObject $args 201 * @return \Zend\EventManager\ResponseCollection All handler return values 202 */ 203 protected function triggerPre($eventName, ArrayObject $args) 204 { 205 return $this->getEventManager()->trigger(new Event($eventName . '.pre', $this, $args)); 206 } 207 208 /** 209 * Triggers the PostEvent and return the result value. 210 * 211 * @param string $eventName 212 * @param ArrayObject $args 213 * @param mixed $result 214 * @return mixed 215 */ 216 protected function triggerPost($eventName, ArrayObject $args, & $result) 217 { 218 $postEvent = new PostEvent($eventName . '.post', $this, $args, $result); 219 $eventRs = $this->getEventManager()->trigger($postEvent); 220 221 return $eventRs->stopped() 222 ? $eventRs->last() 223 : $postEvent->getResult(); 224 } 225 226 /** 227 * Trigger an exception event 228 * 229 * If the ExceptionEvent has the flag "throwException" enabled throw the 230 * exception after trigger else return the result. 231 * 232 * @param string $eventName 233 * @param ArrayObject $args 234 * @param mixed $result 235 * @param \Exception $exception 236 * @throws Exception\ExceptionInterface 237 * @return mixed 238 */ 239 protected function triggerException($eventName, ArrayObject $args, & $result, \Exception $exception) 240 { 241 $exceptionEvent = new ExceptionEvent($eventName . '.exception', $this, $args, $result, $exception); 242 $eventRs = $this->getEventManager()->trigger($exceptionEvent); 243 244 if ($exceptionEvent->getThrowException()) { 245 throw $exceptionEvent->getException(); 246 } 247 248 return $eventRs->stopped() 249 ? $eventRs->last() 250 : $exceptionEvent->getResult(); 251 } 252 253 /** 254 * Check if a plugin is registered 255 * 256 * @param Plugin\PluginInterface $plugin 257 * @return bool 258 */ 259 public function hasPlugin(Plugin\PluginInterface $plugin) 260 { 261 $registry = $this->getPluginRegistry(); 262 return $registry->contains($plugin); 263 } 264 265 /** 266 * Register a plugin 267 * 268 * @param Plugin\PluginInterface $plugin 269 * @param int $priority 270 * @return AbstractAdapter Fluent interface 271 * @throws Exception\LogicException 272 */ 273 public function addPlugin(Plugin\PluginInterface $plugin, $priority = 1) 274 { 275 $registry = $this->getPluginRegistry(); 276 if ($registry->contains($plugin)) { 277 throw new Exception\LogicException(sprintf( 278 'Plugin of type "%s" already registered', 279 get_class($plugin) 280 )); 281 } 282 283 $plugin->attach($this->getEventManager(), $priority); 284 $registry->attach($plugin); 285 286 return $this; 287 } 288 289 /** 290 * Unregister an already registered plugin 291 * 292 * @param Plugin\PluginInterface $plugin 293 * @return AbstractAdapter Fluent interface 294 * @throws Exception\LogicException 295 */ 296 public function removePlugin(Plugin\PluginInterface $plugin) 297 { 298 $registry = $this->getPluginRegistry(); 299 if ($registry->contains($plugin)) { 300 $plugin->detach($this->getEventManager()); 301 $registry->detach($plugin); 302 } 303 return $this; 304 } 305 306 /** 307 * Return registry of plugins 308 * 309 * @return SplObjectStorage 310 */ 311 public function getPluginRegistry() 312 { 313 if (!$this->pluginRegistry instanceof SplObjectStorage) { 314 $this->pluginRegistry = new SplObjectStorage(); 315 } 316 return $this->pluginRegistry; 317 } 318 319 /* reading */ 320 321 /** 322 * Get an item. 323 * 324 * @param string $key 325 * @param bool $success 326 * @param mixed $casToken 327 * @return mixed Data on success, null on failure 328 * @throws Exception\ExceptionInterface 329 * 330 * @triggers getItem.pre(PreEvent) 331 * @triggers getItem.post(PostEvent) 332 * @triggers getItem.exception(ExceptionEvent) 333 */ 334 public function getItem($key, & $success = null, & $casToken = null) 335 { 336 if (!$this->getOptions()->getReadable()) { 337 $success = false; 338 return; 339 } 340 341 $this->normalizeKey($key); 342 343 $argn = func_num_args(); 344 $args = array( 345 'key' => & $key, 346 ); 347 if ($argn > 1) { 348 $args['success'] = & $success; 349 } 350 if ($argn > 2) { 351 $args['casToken'] = & $casToken; 352 } 353 $args = new ArrayObject($args); 354 355 try { 356 $eventRs = $this->triggerPre(__FUNCTION__, $args); 357 358 if ($eventRs->stopped()) { 359 $result = $eventRs->last(); 360 } elseif ($args->offsetExists('success') && $args->offsetExists('casToken')) { 361 $result = $this->internalGetItem($args['key'], $args['success'], $args['casToken']); 362 } elseif ($args->offsetExists('success')) { 363 $result = $this->internalGetItem($args['key'], $args['success']); 364 } else { 365 $result = $this->internalGetItem($args['key']); 366 } 367 368 return $this->triggerPost(__FUNCTION__, $args, $result); 369 } catch (\Exception $e) { 370 $result = null; 371 $success = false; 372 return $this->triggerException(__FUNCTION__, $args, $result, $e); 373 } 374 } 375 376 /** 377 * Internal method to get an item. 378 * 379 * @param string $normalizedKey 380 * @param bool $success 381 * @param mixed $casToken 382 * @return mixed Data on success, null on failure 383 * @throws Exception\ExceptionInterface 384 */ 385 abstract protected function internalGetItem(& $normalizedKey, & $success = null, & $casToken = null); 386 387 /** 388 * Get multiple items. 389 * 390 * @param array $keys 391 * @return array Associative array of keys and values 392 * @throws Exception\ExceptionInterface 393 * 394 * @triggers getItems.pre(PreEvent) 395 * @triggers getItems.post(PostEvent) 396 * @triggers getItems.exception(ExceptionEvent) 397 */ 398 public function getItems(array $keys) 399 { 400 if (!$this->getOptions()->getReadable()) { 401 return array(); 402 } 403 404 $this->normalizeKeys($keys); 405 $args = new ArrayObject(array( 406 'keys' => & $keys, 407 )); 408 409 try { 410 $eventRs = $this->triggerPre(__FUNCTION__, $args); 411 412 $result = $eventRs->stopped() 413 ? $eventRs->last() 414 : $this->internalGetItems($args['keys']); 415 416 return $this->triggerPost(__FUNCTION__, $args, $result); 417 } catch (\Exception $e) { 418 $result = array(); 419 return $this->triggerException(__FUNCTION__, $args, $result, $e); 420 } 421 } 422 423 /** 424 * Internal method to get multiple items. 425 * 426 * @param array $normalizedKeys 427 * @return array Associative array of keys and values 428 * @throws Exception\ExceptionInterface 429 */ 430 protected function internalGetItems(array & $normalizedKeys) 431 { 432 $success = null; 433 $result = array(); 434 foreach ($normalizedKeys as $normalizedKey) { 435 $value = $this->internalGetItem($normalizedKey, $success); 436 if ($success) { 437 $result[$normalizedKey] = $value; 438 } 439 } 440 441 return $result; 442 } 443 444 /** 445 * Test if an item exists. 446 * 447 * @param string $key 448 * @return bool 449 * @throws Exception\ExceptionInterface 450 * 451 * @triggers hasItem.pre(PreEvent) 452 * @triggers hasItem.post(PostEvent) 453 * @triggers hasItem.exception(ExceptionEvent) 454 */ 455 public function hasItem($key) 456 { 457 if (!$this->getOptions()->getReadable()) { 458 return false; 459 } 460 461 $this->normalizeKey($key); 462 $args = new ArrayObject(array( 463 'key' => & $key, 464 )); 465 466 try { 467 $eventRs = $this->triggerPre(__FUNCTION__, $args); 468 469 $result = $eventRs->stopped() 470 ? $eventRs->last() 471 : $this->internalHasItem($args['key']); 472 473 return $this->triggerPost(__FUNCTION__, $args, $result); 474 } catch (\Exception $e) { 475 $result = false; 476 return $this->triggerException(__FUNCTION__, $args, $result, $e); 477 } 478 } 479 480 /** 481 * Internal method to test if an item exists. 482 * 483 * @param string $normalizedKey 484 * @return bool 485 * @throws Exception\ExceptionInterface 486 */ 487 protected function internalHasItem(& $normalizedKey) 488 { 489 $success = null; 490 $this->internalGetItem($normalizedKey, $success); 491 return $success; 492 } 493 494 /** 495 * Test multiple items. 496 * 497 * @param array $keys 498 * @return array Array of found keys 499 * @throws Exception\ExceptionInterface 500 * 501 * @triggers hasItems.pre(PreEvent) 502 * @triggers hasItems.post(PostEvent) 503 * @triggers hasItems.exception(ExceptionEvent) 504 */ 505 public function hasItems(array $keys) 506 { 507 if (!$this->getOptions()->getReadable()) { 508 return array(); 509 } 510 511 $this->normalizeKeys($keys); 512 $args = new ArrayObject(array( 513 'keys' => & $keys, 514 )); 515 516 try { 517 $eventRs = $this->triggerPre(__FUNCTION__, $args); 518 519 $result = $eventRs->stopped() 520 ? $eventRs->last() 521 : $this->internalHasItems($args['keys']); 522 523 return $this->triggerPost(__FUNCTION__, $args, $result); 524 } catch (\Exception $e) { 525 $result = array(); 526 return $this->triggerException(__FUNCTION__, $args, $result, $e); 527 } 528 } 529 530 /** 531 * Internal method to test multiple items. 532 * 533 * @param array $normalizedKeys 534 * @return array Array of found keys 535 * @throws Exception\ExceptionInterface 536 */ 537 protected function internalHasItems(array & $normalizedKeys) 538 { 539 $result = array(); 540 foreach ($normalizedKeys as $normalizedKey) { 541 if ($this->internalHasItem($normalizedKey)) { 542 $result[] = $normalizedKey; 543 } 544 } 545 return $result; 546 } 547 548 /** 549 * Get metadata of an item. 550 * 551 * @param string $key 552 * @return array|bool Metadata on success, false on failure 553 * @throws Exception\ExceptionInterface 554 * 555 * @triggers getMetadata.pre(PreEvent) 556 * @triggers getMetadata.post(PostEvent) 557 * @triggers getMetadata.exception(ExceptionEvent) 558 */ 559 public function getMetadata($key) 560 { 561 if (!$this->getOptions()->getReadable()) { 562 return false; 563 } 564 565 $this->normalizeKey($key); 566 $args = new ArrayObject(array( 567 'key' => & $key, 568 )); 569 570 try { 571 $eventRs = $this->triggerPre(__FUNCTION__, $args); 572 573 $result = $eventRs->stopped() 574 ? $eventRs->last() 575 : $this->internalGetMetadata($args['key']); 576 577 return $this->triggerPost(__FUNCTION__, $args, $result); 578 } catch (\Exception $e) { 579 $result = false; 580 return $this->triggerException(__FUNCTION__, $args, $result, $e); 581 } 582 } 583 584 /** 585 * Internal method to get metadata of an item. 586 * 587 * @param string $normalizedKey 588 * @return array|bool Metadata on success, false on failure 589 * @throws Exception\ExceptionInterface 590 */ 591 protected function internalGetMetadata(& $normalizedKey) 592 { 593 if (!$this->internalHasItem($normalizedKey)) { 594 return false; 595 } 596 597 return array(); 598 } 599 600 /** 601 * Get multiple metadata 602 * 603 * @param array $keys 604 * @return array Associative array of keys and metadata 605 * @throws Exception\ExceptionInterface 606 * 607 * @triggers getMetadatas.pre(PreEvent) 608 * @triggers getMetadatas.post(PostEvent) 609 * @triggers getMetadatas.exception(ExceptionEvent) 610 */ 611 public function getMetadatas(array $keys) 612 { 613 if (!$this->getOptions()->getReadable()) { 614 return array(); 615 } 616 617 $this->normalizeKeys($keys); 618 $args = new ArrayObject(array( 619 'keys' => & $keys, 620 )); 621 622 try { 623 $eventRs = $this->triggerPre(__FUNCTION__, $args); 624 625 $result = $eventRs->stopped() 626 ? $eventRs->last() 627 : $this->internalGetMetadatas($args['keys']); 628 629 return $this->triggerPost(__FUNCTION__, $args, $result); 630 } catch (\Exception $e) { 631 $result = array(); 632 return $this->triggerException(__FUNCTION__, $args, $result, $e); 633 } 634 } 635 636 /** 637 * Internal method to get multiple metadata 638 * 639 * @param array $normalizedKeys 640 * @return array Associative array of keys and metadata 641 * @throws Exception\ExceptionInterface 642 */ 643 protected function internalGetMetadatas(array & $normalizedKeys) 644 { 645 $result = array(); 646 foreach ($normalizedKeys as $normalizedKey) { 647 $metadata = $this->internalGetMetadata($normalizedKey); 648 if ($metadata !== false) { 649 $result[$normalizedKey] = $metadata; 650 } 651 } 652 return $result; 653 } 654 655 /* writing */ 656 657 /** 658 * Store an item. 659 * 660 * @param string $key 661 * @param mixed $value 662 * @return bool 663 * @throws Exception\ExceptionInterface 664 * 665 * @triggers setItem.pre(PreEvent) 666 * @triggers setItem.post(PostEvent) 667 * @triggers setItem.exception(ExceptionEvent) 668 */ 669 public function setItem($key, $value) 670 { 671 if (!$this->getOptions()->getWritable()) { 672 return false; 673 } 674 675 $this->normalizeKey($key); 676 $args = new ArrayObject(array( 677 'key' => & $key, 678 'value' => & $value, 679 )); 680 681 try { 682 $eventRs = $this->triggerPre(__FUNCTION__, $args); 683 684 $result = $eventRs->stopped() 685 ? $eventRs->last() 686 : $this->internalSetItem($args['key'], $args['value']); 687 688 return $this->triggerPost(__FUNCTION__, $args, $result); 689 } catch (\Exception $e) { 690 $result = false; 691 return $this->triggerException(__FUNCTION__, $args, $result, $e); 692 } 693 } 694 695 /** 696 * Internal method to store an item. 697 * 698 * @param string $normalizedKey 699 * @param mixed $value 700 * @return bool 701 * @throws Exception\ExceptionInterface 702 */ 703 abstract protected function internalSetItem(& $normalizedKey, & $value); 704 705 /** 706 * Store multiple items. 707 * 708 * @param array $keyValuePairs 709 * @return array Array of not stored keys 710 * @throws Exception\ExceptionInterface 711 * 712 * @triggers setItems.pre(PreEvent) 713 * @triggers setItems.post(PostEvent) 714 * @triggers setItems.exception(ExceptionEvent) 715 */ 716 public function setItems(array $keyValuePairs) 717 { 718 if (!$this->getOptions()->getWritable()) { 719 return array_keys($keyValuePairs); 720 } 721 722 $this->normalizeKeyValuePairs($keyValuePairs); 723 $args = new ArrayObject(array( 724 'keyValuePairs' => & $keyValuePairs, 725 )); 726 727 try { 728 $eventRs = $this->triggerPre(__FUNCTION__, $args); 729 730 $result = $eventRs->stopped() 731 ? $eventRs->last() 732 : $this->internalSetItems($args['keyValuePairs']); 733 734 return $this->triggerPost(__FUNCTION__, $args, $result); 735 } catch (\Exception $e) { 736 $result = array_keys($keyValuePairs); 737 return $this->triggerException(__FUNCTION__, $args, $result, $e); 738 } 739 } 740 741 /** 742 * Internal method to store multiple items. 743 * 744 * @param array $normalizedKeyValuePairs 745 * @return array Array of not stored keys 746 * @throws Exception\ExceptionInterface 747 */ 748 protected function internalSetItems(array & $normalizedKeyValuePairs) 749 { 750 $failedKeys = array(); 751 foreach ($normalizedKeyValuePairs as $normalizedKey => $value) { 752 if (!$this->internalSetItem($normalizedKey, $value)) { 753 $failedKeys[] = $normalizedKey; 754 } 755 } 756 return $failedKeys; 757 } 758 759 /** 760 * Add an item. 761 * 762 * @param string $key 763 * @param mixed $value 764 * @return bool 765 * @throws Exception\ExceptionInterface 766 * 767 * @triggers addItem.pre(PreEvent) 768 * @triggers addItem.post(PostEvent) 769 * @triggers addItem.exception(ExceptionEvent) 770 */ 771 public function addItem($key, $value) 772 { 773 if (!$this->getOptions()->getWritable()) { 774 return false; 775 } 776 777 $this->normalizeKey($key); 778 $args = new ArrayObject(array( 779 'key' => & $key, 780 'value' => & $value, 781 )); 782 783 try { 784 $eventRs = $this->triggerPre(__FUNCTION__, $args); 785 786 $result = $eventRs->stopped() 787 ? $eventRs->last() 788 : $this->internalAddItem($args['key'], $args['value']); 789 790 return $this->triggerPost(__FUNCTION__, $args, $result); 791 } catch (\Exception $e) { 792 $result = false; 793 return $this->triggerException(__FUNCTION__, $args, $result, $e); 794 } 795 } 796 797 /** 798 * Internal method to add an item. 799 * 800 * @param string $normalizedKey 801 * @param mixed $value 802 * @return bool 803 * @throws Exception\ExceptionInterface 804 */ 805 protected function internalAddItem(& $normalizedKey, & $value) 806 { 807 if ($this->internalHasItem($normalizedKey)) { 808 return false; 809 } 810 return $this->internalSetItem($normalizedKey, $value); 811 } 812 813 /** 814 * Add multiple items. 815 * 816 * @param array $keyValuePairs 817 * @return array Array of not stored keys 818 * @throws Exception\ExceptionInterface 819 * 820 * @triggers addItems.pre(PreEvent) 821 * @triggers addItems.post(PostEvent) 822 * @triggers addItems.exception(ExceptionEvent) 823 */ 824 public function addItems(array $keyValuePairs) 825 { 826 if (!$this->getOptions()->getWritable()) { 827 return array_keys($keyValuePairs); 828 } 829 830 $this->normalizeKeyValuePairs($keyValuePairs); 831 $args = new ArrayObject(array( 832 'keyValuePairs' => & $keyValuePairs, 833 )); 834 835 try { 836 $eventRs = $this->triggerPre(__FUNCTION__, $args); 837 838 $result = $eventRs->stopped() 839 ? $eventRs->last() 840 : $this->internalAddItems($args['keyValuePairs']); 841 842 return $this->triggerPost(__FUNCTION__, $args, $result); 843 } catch (\Exception $e) { 844 $result = array_keys($keyValuePairs); 845 return $this->triggerException(__FUNCTION__, $args, $result, $e); 846 } 847 } 848 849 /** 850 * Internal method to add multiple items. 851 * 852 * @param array $normalizedKeyValuePairs 853 * @return array Array of not stored keys 854 * @throws Exception\ExceptionInterface 855 */ 856 protected function internalAddItems(array & $normalizedKeyValuePairs) 857 { 858 $result = array(); 859 foreach ($normalizedKeyValuePairs as $normalizedKey => $value) { 860 if (!$this->internalAddItem($normalizedKey, $value)) { 861 $result[] = $normalizedKey; 862 } 863 } 864 return $result; 865 } 866 867 /** 868 * Replace an existing item. 869 * 870 * @param string $key 871 * @param mixed $value 872 * @return bool 873 * @throws Exception\ExceptionInterface 874 * 875 * @triggers replaceItem.pre(PreEvent) 876 * @triggers replaceItem.post(PostEvent) 877 * @triggers replaceItem.exception(ExceptionEvent) 878 */ 879 public function replaceItem($key, $value) 880 { 881 if (!$this->getOptions()->getWritable()) { 882 return false; 883 } 884 885 $this->normalizeKey($key); 886 $args = new ArrayObject(array( 887 'key' => & $key, 888 'value' => & $value, 889 )); 890 891 try { 892 $eventRs = $this->triggerPre(__FUNCTION__, $args); 893 894 $result = $eventRs->stopped() 895 ? $eventRs->last() 896 : $this->internalReplaceItem($args['key'], $args['value']); 897 898 return $this->triggerPost(__FUNCTION__, $args, $result); 899 } catch (\Exception $e) { 900 $result = false; 901 return $this->triggerException(__FUNCTION__, $args, $result, $e); 902 } 903 } 904 905 /** 906 * Internal method to replace an existing item. 907 * 908 * @param string $normalizedKey 909 * @param mixed $value 910 * @return bool 911 * @throws Exception\ExceptionInterface 912 */ 913 protected function internalReplaceItem(& $normalizedKey, & $value) 914 { 915 if (!$this->internalhasItem($normalizedKey)) { 916 return false; 917 } 918 919 return $this->internalSetItem($normalizedKey, $value); 920 } 921 922 /** 923 * Replace multiple existing items. 924 * 925 * @param array $keyValuePairs 926 * @return array Array of not stored keys 927 * @throws Exception\ExceptionInterface 928 * 929 * @triggers replaceItems.pre(PreEvent) 930 * @triggers replaceItems.post(PostEvent) 931 * @triggers replaceItems.exception(ExceptionEvent) 932 */ 933 public function replaceItems(array $keyValuePairs) 934 { 935 if (!$this->getOptions()->getWritable()) { 936 return array_keys($keyValuePairs); 937 } 938 939 $this->normalizeKeyValuePairs($keyValuePairs); 940 $args = new ArrayObject(array( 941 'keyValuePairs' => & $keyValuePairs, 942 )); 943 944 try { 945 $eventRs = $this->triggerPre(__FUNCTION__, $args); 946 947 $result = $eventRs->stopped() 948 ? $eventRs->last() 949 : $this->internalReplaceItems($args['keyValuePairs']); 950 951 return $this->triggerPost(__FUNCTION__, $args, $result); 952 } catch (\Exception $e) { 953 $result = array_keys($keyValuePairs); 954 return $this->triggerException(__FUNCTION__, $args, $result, $e); 955 } 956 } 957 958 /** 959 * Internal method to replace multiple existing items. 960 * 961 * @param array $normalizedKeyValuePairs 962 * @return array Array of not stored keys 963 * @throws Exception\ExceptionInterface 964 */ 965 protected function internalReplaceItems(array & $normalizedKeyValuePairs) 966 { 967 $result = array(); 968 foreach ($normalizedKeyValuePairs as $normalizedKey => $value) { 969 if (!$this->internalReplaceItem($normalizedKey, $value)) { 970 $result[] = $normalizedKey; 971 } 972 } 973 return $result; 974 } 975 976 /** 977 * Set an item only if token matches 978 * 979 * It uses the token received from getItem() to check if the item has 980 * changed before overwriting it. 981 * 982 * @param mixed $token 983 * @param string $key 984 * @param mixed $value 985 * @return bool 986 * @throws Exception\ExceptionInterface 987 * @see getItem() 988 * @see setItem() 989 */ 990 public function checkAndSetItem($token, $key, $value) 991 { 992 if (!$this->getOptions()->getWritable()) { 993 return false; 994 } 995 996 $this->normalizeKey($key); 997 $args = new ArrayObject(array( 998 'token' => & $token, 999 'key' => & $key, 1000 'value' => & $value, 1001 )); 1002 1003 try { 1004 $eventRs = $this->triggerPre(__FUNCTION__, $args); 1005 1006 $result = $eventRs->stopped() 1007 ? $eventRs->last() 1008 : $this->internalCheckAndSetItem($args['token'], $args['key'], $args['value']); 1009 1010 return $this->triggerPost(__FUNCTION__, $args, $result); 1011 } catch (\Exception $e) { 1012 $result = false; 1013 return $this->triggerException(__FUNCTION__, $args, $result, $e); 1014 } 1015 } 1016 1017 /** 1018 * Internal method to set an item only if token matches 1019 * 1020 * @param mixed $token 1021 * @param string $normalizedKey 1022 * @param mixed $value 1023 * @return bool 1024 * @throws Exception\ExceptionInterface 1025 * @see getItem() 1026 * @see setItem() 1027 */ 1028 protected function internalCheckAndSetItem(& $token, & $normalizedKey, & $value) 1029 { 1030 $oldValue = $this->internalGetItem($normalizedKey); 1031 if ($oldValue !== $token) { 1032 return false; 1033 } 1034 1035 return $this->internalSetItem($normalizedKey, $value); 1036 } 1037 1038 /** 1039 * Reset lifetime of an item 1040 * 1041 * @param string $key 1042 * @return bool 1043 * @throws Exception\ExceptionInterface 1044 * 1045 * @triggers touchItem.pre(PreEvent) 1046 * @triggers touchItem.post(PostEvent) 1047 * @triggers touchItem.exception(ExceptionEvent) 1048 */ 1049 public function touchItem($key) 1050 { 1051 if (!$this->getOptions()->getWritable()) { 1052 return false; 1053 } 1054 1055 $this->normalizeKey($key); 1056 $args = new ArrayObject(array( 1057 'key' => & $key, 1058 )); 1059 1060 try { 1061 $eventRs = $this->triggerPre(__FUNCTION__, $args); 1062 1063 $result = $eventRs->stopped() 1064 ? $eventRs->last() 1065 : $this->internalTouchItem($args['key']); 1066 1067 return $this->triggerPost(__FUNCTION__, $args, $result); 1068 } catch (\Exception $e) { 1069 $result = false; 1070 return $this->triggerException(__FUNCTION__, $args, $result, $e); 1071 } 1072 } 1073 1074 /** 1075 * Internal method to reset lifetime of an item 1076 * 1077 * @param string $normalizedKey 1078 * @return bool 1079 * @throws Exception\ExceptionInterface 1080 */ 1081 protected function internalTouchItem(& $normalizedKey) 1082 { 1083 $success = null; 1084 $value = $this->internalGetItem($normalizedKey, $success); 1085 if (!$success) { 1086 return false; 1087 } 1088 1089 return $this->internalReplaceItem($normalizedKey, $value); 1090 } 1091 1092 /** 1093 * Reset lifetime of multiple items. 1094 * 1095 * @param array $keys 1096 * @return array Array of not updated keys 1097 * @throws Exception\ExceptionInterface 1098 * 1099 * @triggers touchItems.pre(PreEvent) 1100 * @triggers touchItems.post(PostEvent) 1101 * @triggers touchItems.exception(ExceptionEvent) 1102 */ 1103 public function touchItems(array $keys) 1104 { 1105 if (!$this->getOptions()->getWritable()) { 1106 return $keys; 1107 } 1108 1109 $this->normalizeKeys($keys); 1110 $args = new ArrayObject(array( 1111 'keys' => & $keys, 1112 )); 1113 1114 try { 1115 $eventRs = $this->triggerPre(__FUNCTION__, $args); 1116 1117 $result = $eventRs->stopped() 1118 ? $eventRs->last() 1119 : $this->internalTouchItems($args['keys']); 1120 1121 return $this->triggerPost(__FUNCTION__, $args, $result); 1122 } catch (\Exception $e) { 1123 return $this->triggerException(__FUNCTION__, $args, $keys, $e); 1124 } 1125 } 1126 1127 /** 1128 * Internal method to reset lifetime of multiple items. 1129 * 1130 * @param array $normalizedKeys 1131 * @return array Array of not updated keys 1132 * @throws Exception\ExceptionInterface 1133 */ 1134 protected function internalTouchItems(array & $normalizedKeys) 1135 { 1136 $result = array(); 1137 foreach ($normalizedKeys as $normalizedKey) { 1138 if (!$this->internalTouchItem($normalizedKey)) { 1139 $result[] = $normalizedKey; 1140 } 1141 } 1142 return $result; 1143 } 1144 1145 /** 1146 * Remove an item. 1147 * 1148 * @param string $key 1149 * @return bool 1150 * @throws Exception\ExceptionInterface 1151 * 1152 * @triggers removeItem.pre(PreEvent) 1153 * @triggers removeItem.post(PostEvent) 1154 * @triggers removeItem.exception(ExceptionEvent) 1155 */ 1156 public function removeItem($key) 1157 { 1158 if (!$this->getOptions()->getWritable()) { 1159 return false; 1160 } 1161 1162 $this->normalizeKey($key); 1163 $args = new ArrayObject(array( 1164 'key' => & $key, 1165 )); 1166 1167 try { 1168 $eventRs = $this->triggerPre(__FUNCTION__, $args); 1169 1170 $result = $eventRs->stopped() 1171 ? $eventRs->last() 1172 : $this->internalRemoveItem($args['key']); 1173 1174 return $this->triggerPost(__FUNCTION__, $args, $result); 1175 } catch (\Exception $e) { 1176 $result = false; 1177 return $this->triggerException(__FUNCTION__, $args, $result, $e); 1178 } 1179 } 1180 1181 /** 1182 * Internal method to remove an item. 1183 * 1184 * @param string $normalizedKey 1185 * @return bool 1186 * @throws Exception\ExceptionInterface 1187 */ 1188 abstract protected function internalRemoveItem(& $normalizedKey); 1189 1190 /** 1191 * Remove multiple items. 1192 * 1193 * @param array $keys 1194 * @return array Array of not removed keys 1195 * @throws Exception\ExceptionInterface 1196 * 1197 * @triggers removeItems.pre(PreEvent) 1198 * @triggers removeItems.post(PostEvent) 1199 * @triggers removeItems.exception(ExceptionEvent) 1200 */ 1201 public function removeItems(array $keys) 1202 { 1203 if (!$this->getOptions()->getWritable()) { 1204 return $keys; 1205 } 1206 1207 $this->normalizeKeys($keys); 1208 $args = new ArrayObject(array( 1209 'keys' => & $keys, 1210 )); 1211 1212 try { 1213 $eventRs = $this->triggerPre(__FUNCTION__, $args); 1214 1215 $result = $eventRs->stopped() 1216 ? $eventRs->last() 1217 : $this->internalRemoveItems($args['keys']); 1218 1219 return $this->triggerPost(__FUNCTION__, $args, $result); 1220 } catch (\Exception $e) { 1221 return $this->triggerException(__FUNCTION__, $args, $keys, $e); 1222 } 1223 } 1224 1225 /** 1226 * Internal method to remove multiple items. 1227 * 1228 * @param array $normalizedKeys 1229 * @return array Array of not removed keys 1230 * @throws Exception\ExceptionInterface 1231 */ 1232 protected function internalRemoveItems(array & $normalizedKeys) 1233 { 1234 $result = array(); 1235 foreach ($normalizedKeys as $normalizedKey) { 1236 if (!$this->internalRemoveItem($normalizedKey)) { 1237 $result[] = $normalizedKey; 1238 } 1239 } 1240 return $result; 1241 } 1242 1243 /** 1244 * Increment an item. 1245 * 1246 * @param string $key 1247 * @param int $value 1248 * @return int|bool The new value on success, false on failure 1249 * @throws Exception\ExceptionInterface 1250 * 1251 * @triggers incrementItem.pre(PreEvent) 1252 * @triggers incrementItem.post(PostEvent) 1253 * @triggers incrementItem.exception(ExceptionEvent) 1254 */ 1255 public function incrementItem($key, $value) 1256 { 1257 if (!$this->getOptions()->getWritable()) { 1258 return false; 1259 } 1260 1261 $this->normalizeKey($key); 1262 $args = new ArrayObject(array( 1263 'key' => & $key, 1264 'value' => & $value, 1265 )); 1266 1267 try { 1268 $eventRs = $this->triggerPre(__FUNCTION__, $args); 1269 1270 $result = $eventRs->stopped() 1271 ? $eventRs->last() 1272 : $this->internalIncrementItem($args['key'], $args['value']); 1273 1274 return $this->triggerPost(__FUNCTION__, $args, $result); 1275 } catch (\Exception $e) { 1276 $result = false; 1277 return $this->triggerException(__FUNCTION__, $args, $result, $e); 1278 } 1279 } 1280 1281 /** 1282 * Internal method to increment an item. 1283 * 1284 * @param string $normalizedKey 1285 * @param int $value 1286 * @return int|bool The new value on success, false on failure 1287 * @throws Exception\ExceptionInterface 1288 */ 1289 protected function internalIncrementItem(& $normalizedKey, & $value) 1290 { 1291 $success = null; 1292 $value = (int) $value; 1293 $get = (int) $this->internalGetItem($normalizedKey, $success); 1294 $newValue = $get + $value; 1295 1296 if ($success) { 1297 $this->internalReplaceItem($normalizedKey, $newValue); 1298 } else { 1299 $this->internalAddItem($normalizedKey, $newValue); 1300 } 1301 1302 return $newValue; 1303 } 1304 1305 /** 1306 * Increment multiple items. 1307 * 1308 * @param array $keyValuePairs 1309 * @return array Associative array of keys and new values 1310 * @throws Exception\ExceptionInterface 1311 * 1312 * @triggers incrementItems.pre(PreEvent) 1313 * @triggers incrementItems.post(PostEvent) 1314 * @triggers incrementItems.exception(ExceptionEvent) 1315 */ 1316 public function incrementItems(array $keyValuePairs) 1317 { 1318 if (!$this->getOptions()->getWritable()) { 1319 return array(); 1320 } 1321 1322 $this->normalizeKeyValuePairs($keyValuePairs); 1323 $args = new ArrayObject(array( 1324 'keyValuePairs' => & $keyValuePairs, 1325 )); 1326 1327 try { 1328 $eventRs = $this->triggerPre(__FUNCTION__, $args); 1329 1330 $result = $eventRs->stopped() 1331 ? $eventRs->last() 1332 : $this->internalIncrementItems($args['keyValuePairs']); 1333 1334 return $this->triggerPost(__FUNCTION__, $args, $result); 1335 } catch (\Exception $e) { 1336 $result = array(); 1337 return $this->triggerException(__FUNCTION__, $args, $result, $e); 1338 } 1339 } 1340 1341 /** 1342 * Internal method to increment multiple items. 1343 * 1344 * @param array $normalizedKeyValuePairs 1345 * @return array Associative array of keys and new values 1346 * @throws Exception\ExceptionInterface 1347 */ 1348 protected function internalIncrementItems(array & $normalizedKeyValuePairs) 1349 { 1350 $result = array(); 1351 foreach ($normalizedKeyValuePairs as $normalizedKey => $value) { 1352 $newValue = $this->internalIncrementItem($normalizedKey, $value); 1353 if ($newValue !== false) { 1354 $result[$normalizedKey] = $newValue; 1355 } 1356 } 1357 return $result; 1358 } 1359 1360 /** 1361 * Decrement an item. 1362 * 1363 * @param string $key 1364 * @param int $value 1365 * @return int|bool The new value on success, false on failure 1366 * @throws Exception\ExceptionInterface 1367 * 1368 * @triggers decrementItem.pre(PreEvent) 1369 * @triggers decrementItem.post(PostEvent) 1370 * @triggers decrementItem.exception(ExceptionEvent) 1371 */ 1372 public function decrementItem($key, $value) 1373 { 1374 if (!$this->getOptions()->getWritable()) { 1375 return false; 1376 } 1377 1378 $this->normalizeKey($key); 1379 $args = new ArrayObject(array( 1380 'key' => & $key, 1381 'value' => & $value, 1382 )); 1383 1384 try { 1385 $eventRs = $this->triggerPre(__FUNCTION__, $args); 1386 1387 $result = $eventRs->stopped() 1388 ? $eventRs->last() 1389 : $this->internalDecrementItem($args['key'], $args['value']); 1390 1391 return $this->triggerPost(__FUNCTION__, $args, $result); 1392 } catch (\Exception $e) { 1393 $result = false; 1394 return $this->triggerException(__FUNCTION__, $args, $result, $e); 1395 } 1396 } 1397 1398 /** 1399 * Internal method to decrement an item. 1400 * 1401 * @param string $normalizedKey 1402 * @param int $value 1403 * @return int|bool The new value on success, false on failure 1404 * @throws Exception\ExceptionInterface 1405 */ 1406 protected function internalDecrementItem(& $normalizedKey, & $value) 1407 { 1408 $success = null; 1409 $value = (int) $value; 1410 $get = (int) $this->internalGetItem($normalizedKey, $success); 1411 $newValue = $get - $value; 1412 1413 if ($success) { 1414 $this->internalReplaceItem($normalizedKey, $newValue); 1415 } else { 1416 $this->internalAddItem($normalizedKey, $newValue); 1417 } 1418 1419 return $newValue; 1420 } 1421 1422 /** 1423 * Decrement multiple items. 1424 * 1425 * @param array $keyValuePairs 1426 * @return array Associative array of keys and new values 1427 * @throws Exception\ExceptionInterface 1428 * 1429 * @triggers incrementItems.pre(PreEvent) 1430 * @triggers incrementItems.post(PostEvent) 1431 * @triggers incrementItems.exception(ExceptionEvent) 1432 */ 1433 public function decrementItems(array $keyValuePairs) 1434 { 1435 if (!$this->getOptions()->getWritable()) { 1436 return array(); 1437 } 1438 1439 $this->normalizeKeyValuePairs($keyValuePairs); 1440 $args = new ArrayObject(array( 1441 'keyValuePairs' => & $keyValuePairs, 1442 )); 1443 1444 try { 1445 $eventRs = $this->triggerPre(__FUNCTION__, $args); 1446 1447 $result = $eventRs->stopped() 1448 ? $eventRs->last() 1449 : $this->internalDecrementItems($args['keyValuePairs']); 1450 1451 return $this->triggerPost(__FUNCTION__, $args, $result); 1452 } catch (\Exception $e) { 1453 $result = array(); 1454 return $this->triggerException(__FUNCTION__, $args, $result, $e); 1455 } 1456 } 1457 1458 /** 1459 * Internal method to decrement multiple items. 1460 * 1461 * @param array $normalizedKeyValuePairs 1462 * @return array Associative array of keys and new values 1463 * @throws Exception\ExceptionInterface 1464 */ 1465 protected function internalDecrementItems(array & $normalizedKeyValuePairs) 1466 { 1467 $result = array(); 1468 foreach ($normalizedKeyValuePairs as $normalizedKey => $value) { 1469 $newValue = $this->decrementItem($normalizedKey, $value); 1470 if ($newValue !== false) { 1471 $result[$normalizedKey] = $newValue; 1472 } 1473 } 1474 return $result; 1475 } 1476 1477 /* status */ 1478 1479 /** 1480 * Get capabilities of this adapter 1481 * 1482 * @return Capabilities 1483 * @triggers getCapabilities.pre(PreEvent) 1484 * @triggers getCapabilities.post(PostEvent) 1485 * @triggers getCapabilities.exception(ExceptionEvent) 1486 */ 1487 public function getCapabilities() 1488 { 1489 $args = new ArrayObject(); 1490 1491 try { 1492 $eventRs = $this->triggerPre(__FUNCTION__, $args); 1493 1494 $result = $eventRs->stopped() 1495 ? $eventRs->last() 1496 : $this->internalGetCapabilities(); 1497 1498 return $this->triggerPost(__FUNCTION__, $args, $result); 1499 } catch (\Exception $e) { 1500 $result = false; 1501 return $this->triggerException(__FUNCTION__, $args, $result, $e); 1502 } 1503 } 1504 1505 /** 1506 * Internal method to get capabilities of this adapter 1507 * 1508 * @return Capabilities 1509 */ 1510 protected function internalGetCapabilities() 1511 { 1512 if ($this->capabilities === null) { 1513 $this->capabilityMarker = new stdClass(); 1514 $this->capabilities = new Capabilities($this, $this->capabilityMarker); 1515 } 1516 return $this->capabilities; 1517 } 1518 1519 /* internal */ 1520 1521 /** 1522 * Validates and normalizes a key 1523 * 1524 * @param string $key 1525 * @return void 1526 * @throws Exception\InvalidArgumentException On an invalid key 1527 */ 1528 protected function normalizeKey(& $key) 1529 { 1530 $key = (string) $key; 1531 1532 if ($key === '') { 1533 throw new Exception\InvalidArgumentException( 1534 "An empty key isn't allowed" 1535 ); 1536 } elseif (($p = $this->getOptions()->getKeyPattern()) && !preg_match($p, $key)) { 1537 throw new Exception\InvalidArgumentException( 1538 "The key '{$key}' doesn't match against pattern '{$p}'" 1539 ); 1540 } 1541 } 1542 1543 /** 1544 * Validates and normalizes multiple keys 1545 * 1546 * @param array $keys 1547 * @return void 1548 * @throws Exception\InvalidArgumentException On an invalid key 1549 */ 1550 protected function normalizeKeys(array & $keys) 1551 { 1552 if (!$keys) { 1553 throw new Exception\InvalidArgumentException( 1554 "An empty list of keys isn't allowed" 1555 ); 1556 } 1557 1558 array_walk($keys, array($this, 'normalizeKey')); 1559 $keys = array_values(array_unique($keys)); 1560 } 1561 1562 /** 1563 * Validates and normalizes an array of key-value pairs 1564 * 1565 * @param array $keyValuePairs 1566 * @return void 1567 * @throws Exception\InvalidArgumentException On an invalid key 1568 */ 1569 protected function normalizeKeyValuePairs(array & $keyValuePairs) 1570 { 1571 $normalizedKeyValuePairs = array(); 1572 foreach ($keyValuePairs as $key => $value) { 1573 $this->normalizeKey($key); 1574 $normalizedKeyValuePairs[$key] = $value; 1575 } 1576 $keyValuePairs = $normalizedKeyValuePairs; 1577 } 1578} 1579