Newer
Older
<?php
/**
Dries Buytaert
committed
* Factory for instantiating and statically caching the correct class 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
Dries Buytaert
committed
* The cache bin for which the cache object should be returned, defaults to
* 'cache'.
Dries Buytaert
committed
* @return DrupalCacheInterface
* The cache object associated with the specified bin.
*/
Dries Buytaert
committed
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);
// We do not use drupal_static() here because we do not want to change the
// storage of a cache bin mid-request.
static $cache_objects;
if (!isset($cache_objects[$bin])) {
$class = variable_get('cache_class_' . $bin);
if (!isset($class)) {
$class = variable_get('cache_default_class', 'DrupalDatabaseCache');
}
$cache_objects[$bin] = new $class($bin);
}
return $cache_objects[$bin];
}
Dries Buytaert
committed
* 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.
* @param $bin
* The cache bin to store the data in. Valid core values are 'cache_block',
Dries Buytaert
committed
* 'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form',
* 'cache_menu', 'cache_page', 'cache_path', 'cache_update' or 'cache' for
* the default cache.
*
Dries Buytaert
committed
* @return
* The cache or FALSE on failure.
function cache_get($cid, $bin = 'cache') {
Dries Buytaert
committed
return cache($bin)->get($cid);
Angie Byron
committed
/**
* 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') {
Dries Buytaert
committed
return cache($bin)->getMultiple($cids);
Angie Byron
committed
}
/**
* Store data in the persistent cache.
*
* 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().
* - cache: Generic cache storage bin (used for variables, theme registry,
* locale date, list of simpletest tests etc).
* - cache_block: Stores the content of various blocks.
* - cache field: Stores the field data belonging to a given object.
* - cache_filter: Stores filtered pieces of content.
* - 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.
* - 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
Dries Buytaert
committed
* - 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.
* @param $cid
* The cache ID of the data to store.
Dries Buytaert
committed
* @param $data
Angie Byron
committed
* The data to store in the cache. Complex data types will be automatically
* serialized before insertion.
Dries Buytaert
committed
* Strings will be stored as plain text and not serialized.
* @param $bin
* The cache bin to store the data in. Valid core values are 'cache_block',
Dries Buytaert
committed
* 'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form',
* 'cache_menu', 'cache_page', 'cache_update' or 'cache' for the default
* cache.
* @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.
*/
Dries Buytaert
committed
function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) {
Dries Buytaert
committed
return cache($bin)->set($cid, $data, $expire);
}
/**
* 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 $bin
* If set, the bin $bin to delete from. Mandatory
* argument if $cid is set.
*
* @param $wildcard
* If $wildcard is TRUE, cache IDs starting with $cid are deleted in
* addition to the exact cache ID specified by $cid. If $wildcard is
* TRUE and $cid is '*' then the entire bin $bin is emptied.
function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) {
if (!isset($cid) && !isset($bin)) {
// Clear the block cache first, so stale data will
// not end up in the page cache.
if (module_exists('block')) {
Dries Buytaert
committed
cache('block')->expire();
Dries Buytaert
committed
cache('page')->expire();
Dries Buytaert
committed
return cache($bin)->clear($cid, $wildcard);
}
Dries Buytaert
committed
/**
* 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) {
Dries Buytaert
committed
return cache($bin)->isEmpty();
Dries Buytaert
committed
}
/**
* Interface for cache implementations.
*
Dries Buytaert
committed
* All cache implementations have to implement this interface.
* DrupalDatabaseCache provides the default implementation, which can be
* consulted as an example.
*
* To make Drupal use your implementation for a certain cache bin, you have to
Dries Buytaert
committed
* 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:
* @code
Dries Buytaert
committed
* variable_set('cache_class_cache_page', 'MyCustomCache');
* @endcode
*
Dries Buytaert
committed
* 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.
* @code
* variable_set('cache_default_class', 'MyCustomCache');
* @endcode
*
Dries Buytaert
committed
* 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
*
Dries Buytaert
committed
* @see cache()
* @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.
Dries Buytaert
committed
* @return
* The cache or FALSE on failure.
*/
function get($cid);
Angie Byron
committed
/**
* 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);
/**
* 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.
*/
Dries Buytaert
committed
function set($cid, $data, $expire = CACHE_PERMANENT);
Dries Buytaert
committed
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();
/**
* 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.
Dries Buytaert
committed
*
* @todo: this method is deprecated, as it's functionality is covered by
* more targeted methods in the interface.
*/
function clear($cid = NULL, $wildcard = FALSE);
Dries Buytaert
committed
/**
* 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();
}
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
/**
* A stub cache implementation.
*
* The stub implementation is needed when database access is not yet available.
* Because Drupal's caching system never requires that cached data be present,
* these stub functions can short-circuit the process and sidestep the need for
* any persistent storage. Using this cache implementation during normal
* operations would have a negative impact on performance.
*
* This also can be used for testing purposes.
*/
class DrupalNullCache implements DrupalCacheInterface {
function __construct($bin) {}
function get($cid) {
return FALSE;
}
function getMultiple(&$cids) {
return array();
}
function set($cid, $data, $expire = CACHE_PERMANENT) {}
function delete($cid) {}
function deleteMultiple(array $cids) {}
function deletePrefix($prefix) {}
function flush() {}
function expire() {}
function garbageCollection() {}
function clear($cid = NULL, $wildcard = FALSE) {}
function isEmpty() {
return TRUE;
}
}
/**
* 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) {
Dries Buytaert
committed
// All cache tables should be prefixed with 'cache_', apart from the
// default 'cache' bin, which would look silly.
if ($bin != 'cache') {
$bin = 'cache_' . $bin;
}
$this->bin = $bin;
}
function get($cid) {
Angie Byron
committed
$cids = array($cid);
$cache = $this->getMultiple($cids);
return reset($cache);
Angie Byron
committed
}
function getMultiple(&$cids) {
Dries Buytaert
committed
try {
// Garbage collection necessary when enforcing a minimum cache lifetime.
$this->garbageCollection($this->bin);
Dries Buytaert
committed
// 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));
Dries Buytaert
committed
$cache = array();
foreach ($result as $item) {
$item = $this->prepareItem($item);
if ($item) {
$cache[$item->cid] = $item;
}
Angie Byron
committed
}
Dries Buytaert
committed
$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();
Angie Byron
committed
}
}
/**
* 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) {
Angie Byron
committed
global $user;
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
Dries Buytaert
committed
// timer. The cache variable is loaded into the $user object by _drupal_session_read()
// 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;
}
Angie Byron
committed
if ($cache->serialized) {
$cache->data = unserialize($cache->data);
}
return $cache;
}
Dries Buytaert
committed
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;
}
Dries Buytaert
committed
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.
}
}
Dries Buytaert
committed
function delete($cid) {
db_delete($this->bin)
->condition('cid', $cid)
->execute();
}
Dries Buytaert
committed
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
547
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);
Dries Buytaert
committed
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();
Dries Buytaert
committed
variable_set('cache_flush_' . $this->bin, 0);
}
}
Dries Buytaert
committed
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
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();
}
else {
if ($wildcard) {
if ($cid == '*') {
Dries Buytaert
committed
$this->flush();
}
else {
Dries Buytaert
committed
$this->deletePrefix($cid);
}
Angie Byron
committed
elseif (is_array($cid)) {
Dries Buytaert
committed
$this->deleteMultiple($cid);
Angie Byron
committed
}
Dries Buytaert
committed
$this->delete($cid);
}
}
Dries Buytaert
committed
function isEmpty() {
$this->garbageCollection();
$query = db_select($this->bin);
$query->addExpression('1');
$result = $query->range(0, 1)
->execute()
->fetchField();
return empty($result);
}