MediaWiki  master
MapCacheLRU.php
Go to the documentation of this file.
1 <?php
24 
36 class MapCacheLRU implements ExpirationAwareness, Serializable {
38  private $cache = [];
40  private $timestamps = [];
42  private $epoch;
43 
45  private $maxCacheKeys;
46 
49 
51  private const RANK_TOP = 1.0;
52 
54  private const SIMPLE = 0;
56  private const FIELDS = 1;
57 
61  public function __construct( int $maxKeys ) {
62  if ( $maxKeys <= 0 ) {
63  throw new InvalidArgumentException( '$maxKeys must be above zero' );
64  }
65 
66  $this->maxCacheKeys = $maxKeys;
67  // Use the current time as the default "as of" timestamp of entries
68  $this->epoch = $this->getCurrentTime();
69  }
70 
77  public static function newFromArray( array $values, $maxKeys ) {
78  $mapCache = new self( $maxKeys );
79  $mapCache->cache = ( count( $values ) > $maxKeys )
80  ? array_slice( $values, -$maxKeys, null, true )
81  : $values;
82 
83  return $mapCache;
84  }
85 
90  public function toArray() {
91  return $this->cache;
92  }
93 
109  public function set( $key, $value, $rank = self::RANK_TOP ) {
110  if ( $this->has( $key ) ) {
111  $this->ping( $key );
112  } elseif ( count( $this->cache ) >= $this->maxCacheKeys ) {
113  reset( $this->cache );
114  $evictKey = key( $this->cache );
115  unset( $this->cache[$evictKey] );
116  unset( $this->timestamps[$evictKey] );
117  }
118 
119  if ( $rank < 1.0 && $rank > 0 ) {
120  $offset = intval( $rank * count( $this->cache ) );
121  $this->cache = array_slice( $this->cache, 0, $offset, true )
122  + [ $key => $value ]
123  + array_slice( $this->cache, $offset, null, true );
124  } else {
125  $this->cache[$key] = $value;
126  }
127 
128  $this->timestamps[$key] = [
129  self::SIMPLE => $this->getCurrentTime(),
130  self::FIELDS => []
131  ];
132  }
133 
142  public function has( $key, $maxAge = INF ) {
143  // Optimization: Forego type check because array_key_exists does it already (T275673)
144  return array_key_exists( $key, $this->cache )
145  && (
146  // Optimization: Avoid expensive getAge/getCurrentTime for common case (T275673)
147  $maxAge === INF
148  || $maxAge <= 0
149  || $this->getAge( $key ) <= $maxAge
150  );
151  }
152 
169  public function get( $key, $maxAge = INF, $default = null ) {
170  if ( !$this->has( $key, $maxAge ) ) {
171  return $default;
172  }
173 
174  $this->ping( $key );
175 
176  return $this->cache[$key];
177  }
178 
185  public function setField( $key, $field, $value, $initRank = self::RANK_TOP ) {
186  if ( $this->has( $key ) ) {
187  $this->ping( $key );
188 
189  if ( !is_array( $this->cache[$key] ) ) {
190  $type = gettype( $this->cache[$key] );
191  throw new UnexpectedValueException( "Cannot add field to non-array value ('$key' is $type)" );
192  }
193  } else {
194  $this->set( $key, [], $initRank );
195  }
196 
197  if ( !is_string( $field ) && !is_int( $field ) ) {
198  trigger_error( "Field keys must be string or integer (key '$key')", E_USER_WARNING );
199  return;
200  }
201 
202  $this->cache[$key][$field] = $value;
203  $this->timestamps[$key][self::FIELDS][$field] = $this->getCurrentTime();
204  }
205 
213  public function hasField( $key, $field, $maxAge = INF ) {
214  $value = $this->get( $key );
215 
216  return is_array( $value )
217  // Optimization: Forego $field type check because array_key_exists does it already (T275673)
218  && array_key_exists( $field, $value )
219  && (
220  $maxAge === INF
221  || $maxAge <= 0
222  || $this->getAge( $key, $field ) <= $maxAge
223  );
224  }
225 
233  public function getField( $key, $field, $maxAge = INF ) {
234  if ( !$this->hasField( $key, $field, $maxAge ) ) {
235  return null;
236  }
237 
238  return $this->cache[$key][$field];
239  }
240 
245  public function getAllKeys() {
246  return array_keys( $this->cache );
247  }
248 
262  public function getWithSetCallback(
263  $key, callable $callback, $rank = self::RANK_TOP, $maxAge = INF
264  ) {
265  if ( $this->has( $key, $maxAge ) ) {
266  $value = $this->get( $key );
267  } else {
268  $value = $callback();
269  if ( $value !== false ) {
270  $this->set( $key, $value, $rank );
271  }
272  }
273 
274  return $value;
275  }
276 
283  public function clear( $keys = null ) {
284  if ( func_num_args() == 0 ) {
285  $this->cache = [];
286  $this->timestamps = [];
287  } else {
288  foreach ( (array)$keys as $key ) {
289  unset( $this->cache[$key] );
290  unset( $this->timestamps[$key] );
291  }
292  }
293  }
294 
301  public function getMaxSize() {
302  return $this->maxCacheKeys;
303  }
304 
312  public function setMaxSize( int $maxKeys ) {
313  if ( $maxKeys <= 0 ) {
314  throw new InvalidArgumentException( '$maxKeys must be above zero' );
315  }
316 
317  $this->maxCacheKeys = $maxKeys;
318  while ( count( $this->cache ) > $this->maxCacheKeys ) {
319  reset( $this->cache );
320  $evictKey = key( $this->cache );
321  unset( $this->cache[$evictKey] );
322  unset( $this->timestamps[$evictKey] );
323  }
324  }
325 
331  private function ping( $key ) {
332  $item = $this->cache[$key];
333  unset( $this->cache[$key] );
334  $this->cache[$key] = $item;
335  }
336 
342  private function getAge( $key, $field = null ) {
343  if ( $field !== null ) {
344  $mtime = $this->timestamps[$key][self::FIELDS][$field] ?? $this->epoch;
345  } else {
346  $mtime = $this->timestamps[$key][self::SIMPLE] ?? $this->epoch;
347  }
348 
349  return ( $this->getCurrentTime() - $mtime );
350  }
351 
352  public function serialize() {
353  return serialize( [
354  'entries' => $this->cache,
355  'timestamps' => $this->timestamps,
356  'maxCacheKeys' => $this->maxCacheKeys,
357  ] );
358  }
359 
360  public function unserialize( $serialized ) {
361  $data = unserialize( $serialized );
362  $this->cache = $data['entries'] ?? [];
363  $this->timestamps = $data['timestamps'] ?? [];
364  // Fallback needed for serializations prior to T218511
365  $this->maxCacheKeys = $data['maxCacheKeys'] ?? ( count( $this->cache ) + 1 );
366  $this->epoch = $this->getCurrentTime();
367  }
368 
373  protected function getCurrentTime() {
374  return $this->wallClockOverride ?: microtime( true );
375  }
376 
381  public function setMockTime( &$time ) {
382  $this->wallClockOverride =& $time;
383  }
384 }
MapCacheLRU\serialize
serialize()
Definition: MapCacheLRU.php:352
MapCacheLRU\$epoch
float $epoch
Default entry timestamp if not specified.
Definition: MapCacheLRU.php:42
MapCacheLRU\ping
ping( $key)
Push an entry to the top of the cache.
Definition: MapCacheLRU.php:331
MapCacheLRU\getField
getField( $key, $field, $maxAge=INF)
Definition: MapCacheLRU.php:233
Wikimedia\LightweightObjectStore\ExpirationAwareness
Generic interface providing Time-To-Live constants for expirable object storage.
Definition: ExpirationAwareness.php:32
$serialized
foreach( $res as $row) $serialized
Definition: testCompression.php:88
MapCacheLRU\getAllKeys
getAllKeys()
Definition: MapCacheLRU.php:245
MapCacheLRU\has
has( $key, $maxAge=INF)
Check if a key exists.
Definition: MapCacheLRU.php:142
MapCacheLRU\unserialize
unserialize( $serialized)
Definition: MapCacheLRU.php:360
MapCacheLRU\$cache
array $cache
Map of (key => value)
Definition: MapCacheLRU.php:38
MapCacheLRU\getAge
getAge( $key, $field=null)
Definition: MapCacheLRU.php:342
MapCacheLRU\hasField
hasField( $key, $field, $maxAge=INF)
Definition: MapCacheLRU.php:213
MapCacheLRU\newFromArray
static newFromArray(array $values, $maxKeys)
Definition: MapCacheLRU.php:77
MapCacheLRU\$maxCacheKeys
int $maxCacheKeys
Max number of entries.
Definition: MapCacheLRU.php:45
MapCacheLRU
Handles a simple LRU key/value map with a maximum number of entries.
Definition: MapCacheLRU.php:36
MapCacheLRU\setMaxSize
setMaxSize(int $maxKeys)
Resize the maximum number of cache entries, removing older entries as needed.
Definition: MapCacheLRU.php:312
MapCacheLRU\$timestamps
array $timestamps
Map of (key => (UNIX timestamp, (field => UNIX timestamp)))
Definition: MapCacheLRU.php:40
MapCacheLRU\getWithSetCallback
getWithSetCallback( $key, callable $callback, $rank=self::RANK_TOP, $maxAge=INF)
Get an item with the given key, producing and setting it if not found.
Definition: MapCacheLRU.php:262
MapCacheLRU\getMaxSize
getMaxSize()
Get the maximum number of keys allowed.
Definition: MapCacheLRU.php:301
MapCacheLRU\$wallClockOverride
float null $wallClockOverride
Definition: MapCacheLRU.php:48
MapCacheLRU\setField
setField( $key, $field, $value, $initRank=self::RANK_TOP)
Definition: MapCacheLRU.php:185
$keys
$keys
Definition: testCompression.php:72
MapCacheLRU\clear
clear( $keys=null)
Clear one or several cache entries, or all cache entries.
Definition: MapCacheLRU.php:283
MapCacheLRU\setMockTime
setMockTime(&$time)
Definition: MapCacheLRU.php:381
MapCacheLRU\toArray
toArray()
Definition: MapCacheLRU.php:90
MapCacheLRU\__construct
__construct(int $maxKeys)
Definition: MapCacheLRU.php:61
MapCacheLRU\getCurrentTime
getCurrentTime()
Definition: MapCacheLRU.php:373
$type
$type
Definition: testCompression.php:52