HashBagOStuff.php 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. <?php
  2. /**
  3. * Per-process memory cache for storing items.
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. * @ingroup Cache
  22. */
  23. /**
  24. * Simple store for keeping values in an associative array for the current process.
  25. *
  26. * Data will not persist and is not shared with other processes.
  27. *
  28. * @ingroup Cache
  29. */
  30. class HashBagOStuff extends MediumSpecificBagOStuff {
  31. /** @var mixed[] */
  32. protected $bag = [];
  33. /** @var int Max entries allowed */
  34. protected $maxCacheKeys;
  35. /** @var string CAS token prefix for this instance */
  36. private $token;
  37. /** @var int CAS token counter */
  38. private static $casCounter = 0;
  39. const KEY_VAL = 0;
  40. const KEY_EXP = 1;
  41. const KEY_CAS = 2;
  42. /**
  43. * @param array $params Additional parameters include:
  44. * - maxKeys : only allow this many keys (using oldest-first eviction)
  45. * @codingStandardsIgnoreStart
  46. * @phan-param array{logger?:Psr\Log\LoggerInterface,asyncHandler?:callable,keyspace?:string,reportDupes?:bool,syncTimeout?:int,segmentationSize?:int,segmentedValueMaxSize?:int,maxKeys?:int} $params
  47. * @codingStandardsIgnoreEnd
  48. */
  49. function __construct( $params = [] ) {
  50. $params['segmentationSize'] = $params['segmentationSize'] ?? INF;
  51. parent::__construct( $params );
  52. $this->token = microtime( true ) . ':' . mt_rand();
  53. $this->maxCacheKeys = $params['maxKeys'] ?? INF;
  54. if ( $this->maxCacheKeys <= 0 ) {
  55. throw new InvalidArgumentException( '$maxKeys parameter must be above zero' );
  56. }
  57. }
  58. protected function doGet( $key, $flags = 0, &$casToken = null ) {
  59. $casToken = null;
  60. if ( !$this->hasKey( $key ) || $this->expire( $key ) ) {
  61. return false;
  62. }
  63. // Refresh key position for maxCacheKeys eviction
  64. $temp = $this->bag[$key];
  65. unset( $this->bag[$key] );
  66. $this->bag[$key] = $temp;
  67. $casToken = $this->bag[$key][self::KEY_CAS];
  68. return $this->bag[$key][self::KEY_VAL];
  69. }
  70. protected function doSet( $key, $value, $exptime = 0, $flags = 0 ) {
  71. // Refresh key position for maxCacheKeys eviction
  72. unset( $this->bag[$key] );
  73. $this->bag[$key] = [
  74. self::KEY_VAL => $value,
  75. self::KEY_EXP => $this->getExpirationAsTimestamp( $exptime ),
  76. self::KEY_CAS => $this->token . ':' . ++self::$casCounter
  77. ];
  78. if ( count( $this->bag ) > $this->maxCacheKeys ) {
  79. reset( $this->bag );
  80. $evictKey = key( $this->bag );
  81. unset( $this->bag[$evictKey] );
  82. }
  83. return true;
  84. }
  85. protected function doAdd( $key, $value, $exptime = 0, $flags = 0 ) {
  86. if ( $this->hasKey( $key ) && !$this->expire( $key ) ) {
  87. return false; // key already set
  88. }
  89. return $this->doSet( $key, $value, $exptime, $flags );
  90. }
  91. protected function doDelete( $key, $flags = 0 ) {
  92. unset( $this->bag[$key] );
  93. return true;
  94. }
  95. public function incr( $key, $value = 1, $flags = 0 ) {
  96. $n = $this->get( $key );
  97. if ( $this->isInteger( $n ) ) {
  98. $n = max( $n + (int)$value, 0 );
  99. $this->bag[$key][self::KEY_VAL] = $n;
  100. return $n;
  101. }
  102. return false;
  103. }
  104. public function decr( $key, $value = 1, $flags = 0 ) {
  105. return $this->incr( $key, -$value, $flags );
  106. }
  107. /**
  108. * Clear all values in cache
  109. */
  110. public function clear() {
  111. $this->bag = [];
  112. }
  113. /**
  114. * @param string $key
  115. * @return bool
  116. */
  117. protected function expire( $key ) {
  118. $et = $this->bag[$key][self::KEY_EXP];
  119. if ( $et == self::TTL_INDEFINITE || $et > $this->getCurrentTime() ) {
  120. return false;
  121. }
  122. $this->doDelete( $key );
  123. return true;
  124. }
  125. /**
  126. * Does this bag have a non-null value for the given key?
  127. *
  128. * @param string $key
  129. * @return bool
  130. * @since 1.27
  131. */
  132. public function hasKey( $key ) {
  133. return isset( $this->bag[$key] );
  134. }
  135. }