class DrupalDatabaseCache

Defines a 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.

Hierarchy

Expanded class hierarchy of DrupalDatabaseCache

1 string reference to 'DrupalDatabaseCache'
_cache_get_object in drupal/includes/cache.inc
Gets the cache object for a cache bin.

File

drupal/includes/cache.inc, line 322
Functions and interfaces for cache handling.

View source
class DrupalDatabaseCache implements DrupalCacheInterface {
  protected $bin;

  /**
   * Constructs a DrupalDatabaseCache object.
   *
   * @param $bin
   *   The cache bin for which the object is created.
   */
  function __construct($bin) {
    $this->bin = $bin;
  }

  /**
   * Implements DrupalCacheInterface::get().
   */
  function get($cid) {
    $cids = array(
      $cid,
    );
    $cache = $this
      ->getMultiple($cids);
    return reset($cache);
  }

  /**
   * Implements DrupalCacheInterface::getMultiple().
   */
  function getMultiple(&$cids) {
    try {

      // Garbage collection necessary when enforcing a minimum cache lifetime.
      $this
        ->garbageCollection($this->bin);

      // 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,
      ));
      $cache = array();
      foreach ($result as $item) {
        $item = $this
          ->prepareItem($item);
        if ($item) {
          $cache[$item->cid] = $item;
        }
      }
      $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();
    }
  }

  /**
   * Garbage collection for get() and getMultiple().
   *
   * @param $bin
   *   The bin being requested.
   */
  protected function garbageCollection() {
    $cache_lifetime = variable_get('cache_lifetime', 0);

    // Clean-up the per-user cache expiration session data, so that the session
    // handler can properly clean-up the session data for anonymous users.
    if (isset($_SESSION['cache_expiration'])) {
      $expire = REQUEST_TIME - $cache_lifetime;
      foreach ($_SESSION['cache_expiration'] as $bin => $timestamp) {
        if ($timestamp < $expire) {
          unset($_SESSION['cache_expiration'][$bin]);
        }
      }
      if (!$_SESSION['cache_expiration']) {
        unset($_SESSION['cache_expiration']);
      }
    }

    // Garbage collection of temporary items is only necessary when enforcing
    // a minimum cache lifetime.
    if (!$cache_lifetime) {
      return;
    }

    // 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 + $cache_lifetime <= 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();
    }
  }

  /**
   * Prepares 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) {
    global $user;
    if (!isset($cache->data)) {
      return FALSE;
    }

    // If the cached data is temporary and subject to a per-user minimum
    // lifetime, compare the cache entry timestamp with the user session
    // cache_expiration timestamp. If the cache entry is too old, ignore it.
    if ($cache->expire != CACHE_PERMANENT && variable_get('cache_lifetime', 0) && isset($_SESSION['cache_expiration'][$this->bin]) && $_SESSION['cache_expiration'][$this->bin] > $cache->created) {

      // Ignore cache data that is too old and thus not valid for this user.
      return FALSE;
    }

    // If the data is permanent or not subject to a minimum cache lifetime,
    // unserialize and return the cached data.
    if ($cache->serialized) {
      $cache->data = unserialize($cache->data);
    }
    return $cache;
  }

  /**
   * Implements DrupalCacheInterface::set().
   */
  function set($cid, $data, $expire = CACHE_PERMANENT) {
    $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;
    }
    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.
    }
  }

  /**
   * Implements DrupalCacheInterface::clear().
   */
  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 session. We then simulate
        // that the cache was flushed for this user by not returning cached
        // data that was cached before the timestamp.
        $_SESSION['cache_expiration'][$this->bin] = 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);
        }
        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);
        }
      }
      else {

        // No minimum cache lifetime, flush all temporary cache entries now.
        db_delete($this->bin)
          ->condition('expire', CACHE_PERMANENT, '<>')
          ->condition('expire', REQUEST_TIME, '<')
          ->execute();
      }
    }
    else {
      if ($wildcard) {
        if ($cid == '*') {

          // Check if $this->bin is a cache table before truncating. Other
          // cache_clear_all() operations throw a PDO error in this situation,
          // so we don't need to verify them first. This ensures that non-cache
          // tables cannot be truncated accidentally.
          if ($this
            ->isValidBin()) {
            db_truncate($this->bin)
              ->execute();
          }
          else {
            throw new Exception(t('Invalid or missing cache bin specified: %bin', array(
              '%bin' => $this->bin,
            )));
          }
        }
        else {
          db_delete($this->bin)
            ->condition('cid', db_like($cid) . '%', 'LIKE')
            ->execute();
        }
      }
      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));
      }
      else {
        db_delete($this->bin)
          ->condition('cid', $cid)
          ->execute();
      }
    }
  }

  /**
   * Implements DrupalCacheInterface::isEmpty().
   */
  function isEmpty() {
    $this
      ->garbageCollection();
    $query = db_select($this->bin);
    $query
      ->addExpression('1');
    $result = $query
      ->range(0, 1)
      ->execute()
      ->fetchField();
    return empty($result);
  }

  /**
   * Checks if $this->bin represents a valid cache table.
   *
   * This check is required to ensure that non-cache tables are not truncated
   * accidentally when calling cache_clear_all().
   *
   * @return boolean
   */
  function isValidBin() {
    if ($this->bin == 'cache' || substr($this->bin, 0, 6) == 'cache_') {

      // Skip schema check for bins with standard table names.
      return TRUE;
    }

    // These fields are required for any cache table.
    $fields = array(
      'cid',
      'data',
      'expire',
      'created',
      'serialized',
    );

    // Load the table schema.
    $schema = drupal_get_schema($this->bin);

    // Confirm that all fields are present.
    return isset($schema['fields']) && !array_diff($fields, array_keys($schema['fields']));
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DrupalDatabaseCache::$bin protected property
DrupalDatabaseCache::clear function Implements DrupalCacheInterface::clear(). Overrides DrupalCacheInterface::clear 1
DrupalDatabaseCache::garbageCollection protected function Garbage collection for get() and getMultiple().
DrupalDatabaseCache::get function Implements DrupalCacheInterface::get(). Overrides DrupalCacheInterface::get 1
DrupalDatabaseCache::getMultiple function Implements DrupalCacheInterface::getMultiple(). Overrides DrupalCacheInterface::getMultiple 1
DrupalDatabaseCache::isEmpty function Implements DrupalCacheInterface::isEmpty(). Overrides DrupalCacheInterface::isEmpty 1
DrupalDatabaseCache::isValidBin function Checks if $this->bin represents a valid cache table.
DrupalDatabaseCache::prepareItem protected function Prepares a cached item.
DrupalDatabaseCache::set function Implements DrupalCacheInterface::set(). Overrides DrupalCacheInterface::set 1
DrupalDatabaseCache::__construct function Constructs a DrupalDatabaseCache object.