cache.inc 17.1 KB
Newer Older
1
<?php
2
// $Id$
3

4 5 6 7 8 9 10 11 12 13 14
/**
 * Get the cache object for a cache bin.
 *
 * 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
 *   The cache bin for which the cache object should be returned.
15 16
 * @return DrupalCacheInterface
 *   The cache object associated with the specified bin.
17 18 19 20
 */
function _cache_get_object($bin) {
  // We do not use drupal_static() here because we do not want to change the
  // storage of a cache bin mid-request.
21
  static $cache_objects;
22
  if (!isset($cache_objects[$bin])) {
23 24
    $class = variable_get('cache_class_' . $bin);
    if (!isset($class)) {
25 26 27 28 29 30 31
      $class = variable_get('cache_default_class', 'DrupalDatabaseCache');
    }
    $cache_objects[$bin] = new $class($bin);
  }
  return $cache_objects[$bin];
}

32
/**
33 34 35 36
 * 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.
37
 *
38
 * @param $cid
39
 *   The cache ID of the data to retrieve.
40 41
 * @param $bin
 *   The cache bin to store the data in. Valid core values are 'cache_block',
42 43 44 45
 *   'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form',
 *   'cache_menu', 'cache_page', 'cache_path', 'cache_update' or 'cache' for
 *   the default cache.
 *
46 47
 * @return
 *   The cache or FALSE on failure.
48
 */
49 50
function cache_get($cid, $bin = 'cache') {
  return _cache_get_object($bin)->get($cid);
51 52
}

53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
/**
 * 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') {
  return _cache_get_object($bin)->getMultiple($cids);
}

68 69 70
/**
 * Store data in the persistent cache.
 *
71 72 73 74 75 76 77 78 79
 * 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().
80
 *
81 82
 *  - cache: Generic cache storage bin (used for variables, theme registry,
 *  locale date, list of simpletest tests etc).
83
 *
84
 *  - cache_block: Stores the content of various blocks.
85
 *
86
 *  - cache field: Stores the field data belonging to a given object.
87
 *
88
 *  - cache_filter: Stores filtered pieces of content.
89
 *
90 91 92
 *  - 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.
93
 *
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
 *  - 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
112 113 114 115
 * - 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.
116
 *
117 118
 * @param $cid
 *   The cache ID of the data to store.
119
 * @param $data
120
 *   The data to store in the cache. Complex data types will be automatically
121
 *   serialized before insertion.
122
 *   Strings will be stored as plain text and not serialized.
123 124
 * @param $bin
 *   The cache bin to store the data in. Valid core values are 'cache_block',
125 126
 *   'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form',
 *   'cache_menu', 'cache_page', 'cache_update' or 'cache' for the default
127
 *   cache.
128 129 130 131 132 133 134 135 136
 * @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.
 */
137 138
function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) {
  return _cache_get_object($bin)->set($cid, $data, $expire);
139 140 141
}

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

173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
/**
 * 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) {
  return _cache_get_object($bin)->isEmpty();
}

188 189 190
/**
 * Interface for cache implementations.
 *
191 192 193
 * All cache implementations have to implement this interface.
 * DrupalDatabaseCache provides the default implementation, which can be
 * consulted as an example.
194 195
 *
 * To make Drupal use your implementation for a certain cache bin, you have to
196 197 198 199
 * 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:
200
 * @code
201
 *  variable_set('cache_class_cache_page', 'MyCustomCache');
202 203
 * @endcode
 *
204 205 206
 * 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.
207 208 209 210
 * @code
 *  variable_set('cache_default_class', 'MyCustomCache');
 * @endcode
 *
211 212 213 214 215 216 217 218 219 220 221
 * 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
 *
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
 * @see _cache_get_object()
 * @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.
241 242
   * @return
   *   The cache or FALSE on failure.
243 244 245
   */
  function get($cid);

246 247 248 249 250 251 252 253 254 255 256 257
  /**
   * 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);

258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
  /**
   * 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.
   */
276
  function set($cid, $data, $expire = CACHE_PERMANENT);
277

278

279 280 281 282 283 284 285 286 287 288 289 290 291
  /**
   * 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.
   */
  function clear($cid = NULL, $wildcard = FALSE);
292 293 294 295 296 297 298 299 300 301 302

  /**
   * 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();
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
}

/**
 * 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) {
    $this->bin = $bin;
  }

  function get($cid) {
319 320 321
    $cids = array($cid);
    $cache = $this->getMultiple($cids);
    return reset($cache);
322 323 324
  }

  function getMultiple(&$cids) {
325 326 327
    try {
      // Garbage collection necessary when enforcing a minimum cache lifetime.
      $this->garbageCollection($this->bin);
328 329 330 331 332 333 334 335 336

      // 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));
337 338 339 340 341 342
      $cache = array();
      foreach ($result as $item) {
        $item = $this->prepareItem($item);
        if ($item) {
          $cache[$item->cid] = $item;
        }
343
      }
344 345 346 347 348 349 350
      $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();
351 352 353 354 355 356 357 358 359 360
    }
  }

  /**
   * Garbage collection for get() and getMultiple().
   *
   * @param $bin
   *   The bin being requested.
   */
  protected function garbageCollection() {
361 362
    global $user;

363
    // Garbage collection necessary when enforcing a minimum cache lifetime.
364 365 366 367 368 369 370 371 372 373
    $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();
    }
374
  }
375

376 377 378 379 380 381 382 383 384 385 386 387 388
  /**
   * 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) {
389 390
    global $user;

391 392 393 394 395 396
    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
397
    // timer. The cache variable is loaded into the $user object by _drupal_session_read()
398 399 400 401 402 403 404
    // 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;
    }

405 406 407
    if ($cache->serialized) {
      $cache->data = unserialize($cache->data);
    }
408 409 410 411

    return $cache;
  }

412
  function set($cid, $data, $expire = CACHE_PERMANENT) {
413 414 415 416 417 418 419 420 421 422 423 424 425 426
    $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;
    }

427 428 429 430 431 432 433 434 435
    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.
    }
436 437 438 439 440 441 442 443
  }

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

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

449 450 451 452 453 454 455 456 457 458 459 460 461 462
        $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);
        }
        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.
          db_delete($this->bin)
            ->condition('expire', CACHE_PERMANENT, '<>')
            ->condition('expire', REQUEST_TIME, '<')
            ->execute();
          variable_set('cache_flush_' . $this->bin, 0);
        }
463
      }
464 465 466
      else {
        // No minimum cache lifetime, flush all temporary cache entries now.
        db_delete($this->bin)
467 468 469
          ->condition('expire', CACHE_PERMANENT, '<>')
          ->condition('expire', REQUEST_TIME, '<')
          ->execute();
470 471 472
      }
    }
    else {
473 474 475 476 477 478
      if ($wildcard) {
        if ($cid == '*') {
          db_truncate($this->bin)->execute();
        }
        else {
          db_delete($this->bin)
479
            ->condition('cid', db_like($cid) . '%', 'LIKE')
480 481
            ->execute();
        }
482
      }
483 484 485 486 487 488 489 490 491
      elseif (is_array($cid)) {
        // Delete in chunks when a large array is passed.
        do {
          db_delete($this->bin)
            ->condition('cid', array_splice($cid, 0, 1000), 'IN')
            ->execute();
        }
        while (count($cid));
      }
492
      else {
493 494
        db_delete($this->bin)
          ->condition('cid', $cid)
495
          ->execute();
496
      }
497 498
    }
  }
499 500 501 502 503 504 505 506 507 508

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