cache.inc 16.7 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 table $table 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
201
202
203
 * @code
 *  variable_set('cache_page', 'MyCustomCache');
 * @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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
 * @code
 *  variable_set('cache_default_class', 'MyCustomCache');
 * @endcode
 *
 * @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.
230
231
   * @return
   *   The cache or FALSE on failure.
232
233
234
   */
  function get($cid);

235
236
237
238
239
240
241
242
243
244
245
246
  /**
   * 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);

247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
  /**
   * 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.
   */
265
  function set($cid, $data, $expire = CACHE_PERMANENT);
266

267

268
269
270
271
272
273
274
275
276
277
278
279
280
  /**
   * 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);
281
282
283
284
285
286
287
288
289
290
291

  /**
   * 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();
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
}

/**
 * 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) {
308
309
310
    try {
      // Garbage collection necessary when enforcing a minimum cache lifetime.
      $this->garbageCollection($this->bin);
311
      $cache = db_query("SELECT data, created, expire, serialized FROM {" . $this->bin . "} WHERE cid = :cid", array(':cid' => $cid))->fetchObject();
312
313
314
315
316
317
318
      return $this->prepareItem($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 FALSE;
    }
319
320
321
  }

  function getMultiple(&$cids) {
322
323
324
325
    try {
      // Garbage collection necessary when enforcing a minimum cache lifetime.
      $this->garbageCollection($this->bin);
      $query = db_select($this->bin);
326
      $query->fields($this->bin, array('cid', 'data', 'created', 'expire', 'serialized'));
327
328
329
330
331
332
333
334
      $query->condition($this->bin . '.cid', $cids, 'IN');
      $result = $query->execute();
      $cache = array();
      foreach ($result as $item) {
        $item = $this->prepareItem($item);
        if ($item) {
          $cache[$item->cid] = $item;
        }
335
      }
336
337
338
339
340
341
342
      $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();
343
344
345
346
347
348
349
350
351
352
    }
  }

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

355
    // Garbage collection necessary when enforcing a minimum cache lifetime.
356
357
358
359
360
361
362
363
364
365
    $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();
    }
366
  }
367

368
369
370
371
372
373
374
375
376
377
378
379
380
  /**
   * 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) {
381
382
    global $user;

383
384
385
386
387
388
    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
389
    // timer. The cache variable is loaded into the $user object by _drupal_session_read()
390
391
392
393
394
395
396
    // 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;
    }

397
398
399
    if ($cache->serialized) {
      $cache->data = unserialize($cache->data);
    }
400
401
402
403

    return $cache;
  }

404
  function set($cid, $data, $expire = CACHE_PERMANENT) {
405
406
407
408
409
410
411
412
413
414
415
416
417
418
    $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;
    }

419
420
421
422
423
424
425
426
427
    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.
    }
428
429
430
431
432
433
434
435
  }

  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
436
        // will be saved into the sessions bin by _drupal_session_write(). We then
437
438
439
        // simulate that the cache was flushed for this user by not returning
        // cached data that was cached before the timestamp.
        $user->cache = REQUEST_TIME;
440

441
442
443
444
445
446
447
448
449
450
451
452
453
454
        $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);
        }
455
      }
456
457
458
      else {
        // No minimum cache lifetime, flush all temporary cache entries now.
        db_delete($this->bin)
459
460
461
          ->condition('expire', CACHE_PERMANENT, '<>')
          ->condition('expire', REQUEST_TIME, '<')
          ->execute();
462
463
464
      }
    }
    else {
465
466
467
468
469
470
      if ($wildcard) {
        if ($cid == '*') {
          db_truncate($this->bin)->execute();
        }
        else {
          db_delete($this->bin)
471
            ->condition('cid', db_like($cid) . '%', 'LIKE')
472
473
            ->execute();
        }
474
      }
475
476
477
478
479
480
481
482
483
      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));
      }
484
      else {
485
486
        db_delete($this->bin)
          ->condition('cid', $cid)
487
          ->execute();
488
      }
489
490
    }
  }
491
492
493
494
495
496
497
498
499
500

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