cache.inc 18.4 KB
Newer Older
1 2
<?php

3
/**
4
 * Factory for instantiating and statically caching the correct class for a cache bin.
5 6 7 8 9 10 11 12
 *
 * By default, this returns an instance of the DrupalDatabaseCache class.
 * Classes implementing DrupalCacheInterface can register themselves both as a
 * default implementation and for specific bins.
 *
 * @see DrupalCacheInterface
 *
 * @param $bin
13 14
 *   The cache bin for which the cache object should be returned, defaults to
 *   'cache'.
15 16
 * @return DrupalCacheInterface
 *   The cache object associated with the specified bin.
17
 */
18 19 20 21 22
function cache($bin = 'cache') {
  // Temporary backwards compatibiltiy layer, allow old style prefixed cache
  // bin names to be passed as arguments.
  $bin = str_replace('cache_', '', $bin);

23 24
  // We do not use drupal_static() here because we do not want to change the
  // storage of a cache bin mid-request.
25
  static $cache_objects;
26
  if (!isset($cache_objects[$bin])) {
27 28
    $class = variable_get('cache_class_' . $bin);
    if (!isset($class)) {
29 30 31 32 33 34 35
      $class = variable_get('cache_default_class', 'DrupalDatabaseCache');
    }
    $cache_objects[$bin] = new $class($bin);
  }
  return $cache_objects[$bin];
}

36
/**
37 38 39 40
 * Return data from the persistent cache
 *
 * Data may be stored as either plain text or as serialized data. cache_get
 * will automatically return unserialized objects and arrays.
41
 *
42
 * @param $cid
43
 *   The cache ID of the data to retrieve.
44 45
 * @param $bin
 *   The cache bin to store the data in. Valid core values are 'cache_block',
46 47 48 49
 *   'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form',
 *   'cache_menu', 'cache_page', 'cache_path', 'cache_update' or 'cache' for
 *   the default cache.
 *
50 51
 * @return
 *   The cache or FALSE on failure.
52
 */
53
function cache_get($cid, $bin = 'cache') {
54
  return cache($bin)->get($cid);
55 56
}

57 58 59 60 61 62 63 64 65 66 67 68
/**
 * Return data from the persistent cache when given an array of cache IDs.
 *
 * @param $cids
 *   An array of cache IDs for the data to retrieve. This is passed by
 *   reference, and will have the IDs successfully returned from cache removed.
 * @param $bin
 *   The cache bin where the data is stored.
 * @return
 *   An array of the items successfully returned from cache indexed by cid.
 */
function cache_get_multiple(array &$cids, $bin = 'cache') {
69
  return cache($bin)->getMultiple($cids);
70 71
}

72 73 74
/**
 * Store data in the persistent cache.
 *
75 76 77 78 79 80 81 82 83
 * The persistent cache is split up into several cache bins. In the default
 * cache implementation, each cache bin corresponds to a database table by the
 * same name. Other implementations might want to store several bins in data
 * structures that get flushed together. While it is not a problem for most
 * cache bins if the entries in them are flushed before their expire time, some
 * might break functionality or are extremely expensive to recalculate. These
 * will be marked with a (*). The other bins expired automatically by core.
 * Contributed modules can add additional bins and get them expired
 * automatically by implementing hook_flush_caches().
84
 *
85 86
 *  - cache: Generic cache storage bin (used for variables, theme registry,
 *  locale date, list of simpletest tests etc).
87
 *
88
 *  - cache_block: Stores the content of various blocks.
89
 *
90
 *  - cache field: Stores the field data belonging to a given object.
91
 *
92
 *  - cache_filter: Stores filtered pieces of content.
93
 *
94 95 96
 *  - cache_form(*): Stores multistep forms. Flushing this bin means that some
 *  forms displayed to users lose their state and the data already submitted
 *  to them.
97
 *
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
 *  - cache_menu: Stores the structure of visible navigation menus per page.
 *
 *  - cache_page: Stores generated pages for anonymous users. It is flushed
 *  very often, whenever a page changes, at least for every ode and comment
 *  submission. This is the only bin affected by the page cache setting on
 *  the administrator panel.
 *
 *  - cache path: Stores the system paths that have an alias.
 *
 *  - cache update(*): Stores available releases. The update server (for
 *  example, drupal.org) needs to produce the relevant XML for every project
 *  installed on the current site. As this is different for (almost) every
 *  site, it's very expensive to recalculate for the update server.
 *
 * The reasons for having several bins are as follows:
 *
 * - smaller bins mean smaller database tables and allow for faster selects and
 *   inserts
116 117 118 119
 * - we try to put fast changing cache items and rather static ones into
 *   different bins. The effect is that only the fast changing bins will need a
 *   lot of writes to disk. The more static bins will also be better cacheable
 *   with MySQL's query cache.
120
 *
121 122
 * @param $cid
 *   The cache ID of the data to store.
123
 * @param $data
124
 *   The data to store in the cache. Complex data types will be automatically
125
 *   serialized before insertion.
126
 *   Strings will be stored as plain text and not serialized.
127 128
 * @param $bin
 *   The cache bin to store the data in. Valid core values are 'cache_block',
129 130
 *   'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form',
 *   'cache_menu', 'cache_page', 'cache_update' or 'cache' for the default
131
 *   cache.
132 133 134 135 136 137 138 139 140
 * @param $expire
 *   One of the following values:
 *   - CACHE_PERMANENT: Indicates that the item should never be removed unless
 *     explicitly told to using cache_clear_all() with a cache ID.
 *   - CACHE_TEMPORARY: Indicates that the item should be removed at the next
 *     general cache wipe.
 *   - A Unix timestamp: Indicates that the item should be kept at least until
 *     the given time, after which it behaves like CACHE_TEMPORARY.
 */
141
function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) {
142
  return cache($bin)->set($cid, $data, $expire);
143 144 145
}

/**
146
 * Expire data from the cache.
147
 *
148 149
 * If called without arguments, expirable entries will be cleared from the
 * cache_page and cache_block bins.
150 151 152 153 154
 *
 * @param $cid
 *   If set, the cache ID to delete. Otherwise, all cache entries that can
 *   expire are deleted.
 *
155 156
 * @param $bin
 *   If set, the bin $bin to delete from. Mandatory
157 158
 *   argument if $cid is set.
 *
159
 * @param $wildcard
160 161
 *   If $wildcard is TRUE, cache IDs starting with $cid are deleted in
 *   addition to the exact cache ID specified by $cid.  If $wildcard is
162
 *   TRUE and $cid is '*' then the entire bin $bin is emptied.
163
 */
164 165
function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) {
  if (!isset($cid) && !isset($bin)) {
166 167
    // Clear the block cache first, so stale data will
    // not end up in the page cache.
168
    if (module_exists('block')) {
169
      cache('block')->expire();
170
    }
171
    cache('page')->expire();
172 173
    return;
  }
174
  return cache($bin)->clear($cid, $wildcard);
175 176
}

177 178 179 180 181 182 183 184 185 186 187 188
/**
 * Check if a cache bin is empty.
 *
 * A cache bin is considered empty if it does not contain any valid data for any
 * cache ID.
 *
 * @param $bin
 *   The cache bin to check.
 * @return
 *   TRUE if the cache bin specified is empty.
 */
function cache_is_empty($bin) {
189
  return cache($bin)->isEmpty();
190 191
}

192 193 194
/**
 * Interface for cache implementations.
 *
195 196 197
 * All cache implementations have to implement this interface.
 * DrupalDatabaseCache provides the default implementation, which can be
 * consulted as an example.
198 199
 *
 * To make Drupal use your implementation for a certain cache bin, you have to
200 201 202 203
 * set a variable with the name of the cache bin as its key and the name of
 * your class as its value. For example, if your implementation of
 * DrupalCacheInterface was called MyCustomCache, the following line would make
 * Drupal use it for the 'cache_page' bin:
204
 * @code
205
 *  variable_set('cache_class_cache_page', 'MyCustomCache');
206 207
 * @endcode
 *
208 209 210
 * Additionally, you can register your cache implementation to be used by
 * default for all cache bins by setting the variable 'cache_default_class' to
 * the name of your implementation of the DrupalCacheInterface, e.g.
211 212 213 214
 * @code
 *  variable_set('cache_default_class', 'MyCustomCache');
 * @endcode
 *
215 216 217 218 219 220 221 222 223 224 225
 * To implement a completely custom cache bin, use the same variable format:
 * @code
 *  variable_set('cache_class_custom_bin', 'MyCustomCache');
 * @endcode
 * To access your custom cache bin, specify the name of the bin when storing
 * or retrieving cached data:
 * @code
 *  cache_set($cid, $data, 'custom_bin', $expire);
 *  cache_get($cid, 'custom_bin');
 * @endcode
 *
226
 * @see cache()
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
 * @see DrupalDatabaseCache
 */
interface DrupalCacheInterface {
  /**
   * Constructor.
   *
   * @param $bin
   *   The cache bin for which the object is created.
   */
  function __construct($bin);

  /**
   * Return data from the persistent cache. Data may be stored as either plain
   * text or as serialized data. cache_get will automatically return
   * unserialized objects and arrays.
   *
   * @param $cid
   *   The cache ID of the data to retrieve.
245 246
   * @return
   *   The cache or FALSE on failure.
247 248 249
   */
  function get($cid);

250 251 252 253 254 255 256 257 258 259 260 261
  /**
   * Return data from the persistent cache when given an array of cache IDs.
   *
   * @param $cids
   *   An array of cache IDs for the data to retrieve. This is passed by
   *   reference, and will have the IDs successfully returned from cache
   *   removed.
   * @return
   *   An array of the items successfully returned from cache indexed by cid.
   */
   function getMultiple(&$cids);

262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
  /**
   * Store data in the persistent cache.
   *
   * @param $cid
   *   The cache ID of the data to store.
   * @param $data
   *   The data to store in the cache. Complex data types will be automatically
   *   serialized before insertion.
   *   Strings will be stored as plain text and not serialized.
   * @param $expire
   *   One of the following values:
   *   - CACHE_PERMANENT: Indicates that the item should never be removed unless
   *     explicitly told to using cache_clear_all() with a cache ID.
   *   - CACHE_TEMPORARY: Indicates that the item should be removed at the next
   *     general cache wipe.
   *   - A Unix timestamp: Indicates that the item should be kept at least until
   *     the given time, after which it behaves like CACHE_TEMPORARY.
   */
280
  function set($cid, $data, $expire = CACHE_PERMANENT);
281

282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
  /**
   * Delete an item from the cache.
   *
   * @param $cid
   *    The cache ID to delete.
   */
  function delete($cid);

  /**
   * Delete multiple items from the cache.
   *
   * @param $cids
   *   An array of $cids to delete.
   */
  function deleteMultiple(Array $cids);

  /**
   * Delete items from the cache using a wildcard prefix.
   *
   * @param $prefix
   *   A wildcard prefix.
   */
  function deletePrefix($prefix);

  /**
   * Flush all cache items in a bin.
   */
  function flush();

  /**
   * Expire temporary items from cache.
   */
  function expire();

  /**
   * Perform garbage collection on a cache bin.
   */
  function garbageCollection();
320

321 322 323 324 325 326 327 328 329 330 331
  /**
   * Expire data from the cache. If called without arguments, expirable
   * entries will be cleared from the cache_page and cache_block bins.
   *
   * @param $cid
   *   If set, the cache ID to delete. Otherwise, all cache entries that can
   *   expire are deleted.
   * @param $wildcard
   *   If set to TRUE, the $cid is treated as a substring
   *   to match rather than a complete ID. The match is a right hand
   *   match. If '*' is given as $cid, the bin $bin will be emptied.
332 333
   *
   * @todo: this method is deprecated, as it's functionality is covered by
334
   * more targeted methods in the interface.
335 336
   */
  function clear($cid = NULL, $wildcard = FALSE);
337 338 339 340 341 342 343 344 345 346 347

  /**
   * Check if a cache bin is empty.
   *
   * A cache bin is considered empty if it does not contain any valid data for
   * any cache ID.
   *
   * @return
   *   TRUE if the cache bin specified is empty.
   */
  function isEmpty();
348 349 350 351 352 353 354 355 356 357 358 359
}

/**
 * Default cache implementation.
 *
 * This is Drupal's default cache implementation. It uses the database to store
 * cached data. Each cache bin corresponds to a database table by the same name.
 */
class DrupalDatabaseCache implements DrupalCacheInterface {
  protected $bin;

  function __construct($bin) {
360 361 362 363 364
    // All cache tables should be prefixed with 'cache_', apart from the
    // default 'cache' bin, which would look silly.
    if ($bin != 'cache') {
      $bin = 'cache_' . $bin;
    }
365 366 367 368
    $this->bin = $bin;
  }

  function get($cid) {
369 370 371
    $cids = array($cid);
    $cache = $this->getMultiple($cids);
    return reset($cache);
372 373 374
  }

  function getMultiple(&$cids) {
375 376 377
    try {
      // Garbage collection necessary when enforcing a minimum cache lifetime.
      $this->garbageCollection($this->bin);
378 379 380 381 382 383 384 385 386

      // When serving cached pages, the overhead of using db_select() was found
      // to add around 30% overhead to the request. Since $this->bin is a
      // variable, this means the call to db_query() here uses a concatenated
      // string. This is highly discouraged under any other circumstances, and
      // is used here only due to the performance overhead we would incur
      // otherwise. When serving an uncached page, the overhead of using
      // db_select() is a much smaller proportion of the request.
      $result = db_query('SELECT cid, data, created, expire, serialized FROM {' . db_escape_table($this->bin) . '} WHERE cid IN (:cids)', array(':cids' => $cids));
387 388 389 390 391 392
      $cache = array();
      foreach ($result as $item) {
        $item = $this->prepareItem($item);
        if ($item) {
          $cache[$item->cid] = $item;
        }
393
      }
394 395 396 397 398 399 400
      $cids = array_diff($cids, array_keys($cache));
      return $cache;
    }
    catch (Exception $e) {
      // If the database is never going to be available, cache requests should
      // return FALSE in order to allow exception handling to occur.
      return array();
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
    }
  }

  /**
   * Prepare a cached item.
   *
   * Checks that items are either permanent or did not expire, and unserializes
   * data as appropriate.
   *
   * @param $cache
   *   An item loaded from cache_get() or cache_get_multiple().
   * @return
   *   The item with data unserialized as appropriate or FALSE if there is no
   *   valid item to load.
   */
  protected function prepareItem($cache) {
417 418
    global $user;

419 420 421 422 423 424
    if (!isset($cache->data)) {
      return FALSE;
    }
    // If enforcing a minimum cache lifetime, validate that the data is
    // currently valid for this user before we return it by making sure the cache
    // entry was created before the timestamp in the current session's cache
425
    // timer. The cache variable is loaded into the $user object by _drupal_session_read()
426 427 428 429 430 431 432
    // in session.inc. If the data is permanent or we're not enforcing a minimum
    // cache lifetime always return the cached data.
    if ($cache->expire != CACHE_PERMANENT && variable_get('cache_lifetime', 0) && $user->cache > $cache->created) {
      // This cache data is too old and thus not valid for us, ignore it.
      return FALSE;
    }

433 434 435
    if ($cache->serialized) {
      $cache->data = unserialize($cache->data);
    }
436 437 438 439

    return $cache;
  }

440
  function set($cid, $data, $expire = CACHE_PERMANENT) {
441 442 443 444 445 446 447 448 449 450 451 452 453 454
    $fields = array(
      'serialized' => 0,
      'created' => REQUEST_TIME,
      'expire' => $expire,
    );
    if (!is_string($data)) {
      $fields['data'] = serialize($data);
      $fields['serialized'] = 1;
    }
    else {
      $fields['data'] = $data;
      $fields['serialized'] = 0;
    }

455 456 457 458 459 460 461 462 463
    try {
      db_merge($this->bin)
        ->key(array('cid' => $cid))
        ->fields($fields)
        ->execute();
    }
    catch (Exception $e) {
      // The database may not be available, so we'll ignore cache_set requests.
    }
464 465
  }

466 467 468 469 470
  function delete($cid) {
    db_delete($this->bin)
      ->condition('cid', $cid)
      ->execute();
  }
471

472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
  function deleteMultiple(Array $cids) {
    // Delete in chunks when a large array is passed.
    do {
      db_delete($this->bin)
        ->condition('cid', array_splice($cids, 0, 1000), 'IN')
        ->execute();
    }
    while (count($cids));
  }

  function deletePrefix($prefix) {
    db_delete($this->bin)
      ->condition('cid', db_like($prefix) . '%', 'LIKE')
      ->execute();
  }

  function flush() {
    db_truncate($this->bin)->execute();
  }

  function expire() {
    if (variable_get('cache_lifetime', 0)) {
      // We store the time in the current user's $user->cache variable which
      // will be saved into the sessions bin by _drupal_session_write(). We then
      // simulate that the cache was flushed for this user by not returning
      // cached data that was cached before the timestamp.
      $GLOBALS['user']->cache = REQUEST_TIME;

      $cache_flush = variable_get('cache_flush_' . $this->bin, 0);
      if ($cache_flush == 0) {
        // This is the first request to clear the cache, start a timer.
        variable_set('cache_flush_' . $this->bin, REQUEST_TIME);
504
      }
505 506 507
      elseif (REQUEST_TIME > ($cache_flush + variable_get('cache_lifetime', 0))) {
        // Clear the cache for everyone, cache_lifetime seconds have
        // passed since the first request to clear the cache.
508
        db_delete($this->bin)
509 510 511
          ->condition('expire', CACHE_PERMANENT, '<>')
          ->condition('expire', REQUEST_TIME, '<')
          ->execute();
512
        variable_set('cache_flush_' . $this->bin, 0);
513 514
      }
    }
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
    else {
      // No minimum cache lifetime, flush all temporary cache entries now.
      db_delete($this->bin)
        ->condition('expire', CACHE_PERMANENT, '<>')
        ->condition('expire', REQUEST_TIME, '<')
        ->execute();
    }
  }

  function garbageCollection() {
    global $user;

    // When cache lifetime is in force, avoid running garbage collection too
    // often since this will remove temporary cache items indiscriminately.
    $cache_flush = variable_get('cache_flush_' . $this->bin, 0);
    if ($cache_flush && ($cache_flush + variable_get('cache_lifetime', 0) <= REQUEST_TIME)) {
      // Reset the variable immediately to prevent a meltdown in heavy load situations.
      variable_set('cache_flush_' . $this->bin, 0);
      // Time to flush old cache data
      db_delete($this->bin)
        ->condition('expire', CACHE_PERMANENT, '<>')
        ->condition('expire', $cache_flush, '<=')
        ->execute();
    }
  }

  function clear($cid = NULL, $wildcard = FALSE) {
    global $user;

    if (empty($cid)) {
      $this->expire();
    }
547
    else {
548 549
      if ($wildcard) {
        if ($cid == '*') {
550
          $this->flush();
551 552
        }
        else {
553
          $this->deletePrefix($cid);
554
        }
555
      }
556
      elseif (is_array($cid)) {
557
        $this->deleteMultiple($cid);
558
      }
559
      else {
560
        $this->delete($cid);
561
      }
562 563
    }
  }
564 565 566 567 568 569 570 571 572 573

  function isEmpty() {
    $this->garbageCollection();
    $query = db_select($this->bin);
    $query->addExpression('1');
    $result = $query->range(0, 1)
      ->execute()
      ->fetchField();
    return empty($result);
  }
574
}