d8cache.module in Drupal 8 Cache Backport 7
Main module file for the D8 caching system backport.
File
d8cache.moduleView source
<?php
/**
* @file
* Main module file for the D8 caching system backport.
*/
// @todo Remove once integrated into D7 core.
require_once __DIR__ . '/includes/core.inc';
require_once __DIR__ . '/includes/core-attachments-collector.inc';
require_once __DIR__ . '/includes/block.inc';
require_once __DIR__ . '/includes/comment.inc';
require_once __DIR__ . '/includes/entity.inc';
require_once __DIR__ . '/includes/menu.inc';
require_once __DIR__ . '/includes/node.inc';
require_once __DIR__ . '/includes/theme.inc';
require_once __DIR__ . '/includes/user.inc';
require_once __DIR__ . '/includes/views.inc';
/* -----------------------------------------------------------------------
* Core Hooks
*/
/**
* Implements hook_process_html().
*/
function d8cache_process_html(&$variables) {
drupal_emit_cache_tags();
drupal_emit_cache_max_age();
}
/**
* Implements hook_ajax_render_alter().
*/
function d8cache_ajax_render_alter(&$commands) {
drupal_emit_cache_tags();
drupal_emit_cache_max_age();
}
/**
* Implements hook_emit_cache_tags().
*/
function d8cache_emit_cache_tags($tags) {
$page_cache_tags =& drupal_static(__FUNCTION__, array());
if (variable_get('d8cache_emit_cache_tags', FALSE)) {
drupal_add_http_header('X-Drupal-Cache-Tags', implode(' ', $tags));
}
// Store the emitted cache tags for further use.
$page_cache_tags = $tags;
}
/**
* Implements hook_emit_cache_max_age().
*/
function d8cache_emit_cache_max_age($max_age) {
$page_cache_max_age =& drupal_static(__FUNCTION__, array());
// Store the emitted cache max_age for further use.
$page_cache_max_age = $max_age;
}
/**
* Backwards compatible version of d8cache_invalidate_cache_tags().
* This will automatically be used when the core patch to implement end of
* transaction callbacks is missing.
* @param $tags
*/
function d8cache_OLD_invalidate_cache_tags($tags) {
$tag_cache =& drupal_static('d8cache_tag_cache', array());
$invalidated_tags =& drupal_static('d8cache_invalidated_tags', array());
foreach ($tags as $tag) {
// Only invalidate tags once per request unless they are written again.
if (isset($invalidated_tags[$tag])) {
continue;
}
$invalidated_tags[$tag] = TRUE;
unset($tag_cache[$tag]);
db_merge('d8cache_cache_tags')
->key(array(
'tag' => $tag,
))
->fields(array(
'invalidations' => 1,
))
->expression('invalidations', 'invalidations + 1')
->execute();
_d8cache_cache_tags_invalidate_cache($tag);
}
}
/**
* Implements hook_flush_caches().
*/
function d8cache_flush_caches() {
return _d8cache_cache_tags_cache_bins();
}
/**
* Check whether the database connection supports root transaction end callbacks.
* @param DatabaseConnection $connection
*
* @return bool
*/
function _d8cache_has_transaction_callbacks($connection) {
return method_exists($connection, 'addRootTransactionEndCallback');
}
/**
* Implements hook_invalidate_cache_tags().
*/
function d8cache_invalidate_cache_tags($tags) {
// Not using drupal_static because the detection is based on the running
// code, and that is not going to change during execution.
static $use_transaction_callback;
$deferred_tags =& drupal_static('d8cache_deferred_tags', array());
$connection = Database::getConnection();
if (!isset($use_transaction_callback)) {
$use_transaction_callback = _d8cache_has_transaction_callbacks($connection);
}
if (!$use_transaction_callback) {
// Core support missing, use the old (deadlock-prone) invalidation code.
return d8cache_OLD_invalidate_cache_tags($tags);
}
if ($connection
->inTransaction()) {
// We are inside a transaction, we must defer until the end of the transaction.
foreach ($tags as $tag) {
if (empty($deferred_tags)) {
$connection
->addRootTransactionEndCallback('d8cache_root_transaction_end');
}
$deferred_tags[$tag] = TRUE;
}
}
else {
// Not in a transction, directly invalidate instead.
d8cache_do_invalidate_cache_tags($tags);
}
}
/**
* Perform cache tag invalidations. Must do outside of a transaction.
*
* @throws \Exception
*/
function d8cache_do_invalidate_cache_tags($tags) {
$tag_cache =& drupal_static('d8cache_tag_cache', array());
$invalidated_tags =& drupal_static('d8cache_invalidated_tags', array());
$connection = Database::getConnection();
if ($connection
->inTransaction()) {
throw new Exception('Must be outside of a transaction!');
}
// Ensure tags are cleared in a deterministic order by sorting them first.
// This prevents an edge case between multiple transactions that can
// cause a deadlock.
$tags = array_unique($tags);
sort($tags);
$transaction = db_transaction();
foreach ($tags as $tag) {
// Only invalidate tags once per request unless they are written again.
if (isset($invalidated_tags[$tag])) {
continue;
}
$invalidated_tags[$tag] = TRUE;
unset($tag_cache[$tag]);
try {
db_merge('d8cache_cache_tags')
->key([
'tag' => $tag,
])
->fields([
'invalidations' => 1,
])
->expression('invalidations', 'invalidations + 1')
->execute();
} catch (Exception $e) {
$transaction
->rollback();
throw $e;
}
}
// COMMIT
unset($transaction);
// Ensure the cache tags cache is invalidated too.
foreach ($tags as $tag) {
_d8cache_cache_tags_invalidate_cache($tag);
}
}
/**
* Callback to invalidate cache tags at the end of a transaction.
*/
function d8cache_root_transaction_end($committed = NULL) {
$deferred_tags =& drupal_static('d8cache_deferred_tags', array());
if ($committed === NULL || $committed === TRUE) {
// If the commit is presumed successful, proceed with the invalidation.
d8cache_do_invalidate_cache_tags(array_keys($deferred_tags));
}
else {
// Since the transaction was rolled back, the deferred tags no longer
// match reality. Since the end result of this transaction was nothing,
// we should also do nothing, to eliminate side effects.
// Whatever transaction beat us to the commit will have also done its
// own invalidation as appropriate, and attempting to invalidate
// things ourselves will just cause additional contention for no reason.
// Therefore, we do not want to act on the deferred tags at all.
//
// @todo Check transaction isolation level and invalidate anyway if the
// database is operating in a non-isolated mode?
}
// In both cases, we reset the deferred_tags list.
$deferred_tags = array();
}
/* -----------------------------------------------------------------------
* Contrib Hooks
*/
/* -----------------------------------------------------------------------
* Public API
*/
/**
* Returns the sum total of validations for a given set of tags.
*
* Called by a backend when storing a cache item.
*
* @param string[] $tags
* Array of cache tags.
*
* @return string
* Cache tag invalidations checksum.
*/
function d8cache_cache_tags_get_current_checksum(array $tags) {
$invalidated_tags =& drupal_static('d8cache_invalidated_tags', array());
// Remove tags that were already invalidated during this request from the
// static caches so that another invalidation can occur later in the same
// request. Without that, written cache items would not be invalidated
// correctly.
foreach ($tags as $tag) {
unset($invalidated_tags[$tag]);
}
return _d8cache_cache_tags_calculate_checksum($tags);
}
/**
* Returns whether the checksum is valid for the given cache tags.
*
* Used when retrieving a cache item in a cache backend, to verify that no
* cache tag based invalidation happened.
*
* @param int $checksum
* The checksum that was stored together with the cache item.
* @param string[] $tags
* The cache tags that were stored together with the cache item.
*
* @return bool
* FALSE if cache tag invalidations happened for the passed in tags since
* the cache item was stored, TRUE otherwise.
*/
function d8cache_cache_tags_is_valid($checksum, array $tags) {
return $checksum == _d8cache_cache_tags_calculate_checksum($tags);
}
/**
* Wraps the internal Drupal API with an official API.
*
* @param string $bin
* The bin to get a cache object for.
*
* @return DrupalCacheInterface
* The DrupalCacheInterface object for the selected bin.
*/
function d8cache_cache_get_object($bin) {
return _cache_get_object($bin);
}
/* -----------------------------------------------------------------------
* Helper functions
*/
/**
* Calculates the current checksum for a given set of tags.
*
* @param array $tags
* The array of tags to calculate the checksum for.
*
* @return int
* The calculated checksum.
*/
function _d8cache_cache_tags_calculate_checksum(array $tags) {
$tag_cache =& drupal_static('d8cache_tag_cache', array());
$checksum = 0;
$query_tags = array_diff($tags, array_keys($tag_cache));
// Try to load from cache first.
if ($query_tags) {
$tag_cache += _d8cache_cache_tags_load_from_cache($query_tags);
}
// Are there any remaining tags to query?
if ($query_tags) {
$result = _d8cache_cache_tags_load_from_db($query_tags);
_d8cache_cache_tags_update_cache($result);
$tag_cache += $result;
}
foreach ($tags as $tag) {
$checksum += $tag_cache[$tag];
}
return $checksum;
}
/**
* Loads cache tag invalidations from the database.
*
* @param array $query_tags
* The tags to query from the database.
*
* @return array
* An array keyed by tag with invalidations as value. Using 0 if no
* invalidation occured, yet.
*/
function _d8cache_cache_tags_load_from_db($query_tags) {
$result = array();
// Ensure database is loaded, since we can get called to load missing tags
// during DRUPAL_BOOTSTRAP_PAGE_CACHE. This is a recursive call.
drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE, FALSE);
if ($db_tags = db_query('SELECT tag, invalidations FROM {d8cache_cache_tags} WHERE tag IN (:tags)', array(
':tags' => $query_tags,
))
->fetchAllKeyed()) {
$result += $db_tags;
}
// Fill static cache with empty objects for tags not found in the database.
$result += array_fill_keys(array_diff($query_tags, array_keys($db_tags)), 0);
return $result;
}
/* -----------------------------------------------------------------------
* Cache helper functions
*/
function _d8cache_cache_tags_cache_bins() {
if (!variable_get('d8cache_use_cache_tags_cache', FALSE)) {
return array();
}
return [
'cache_d8cache_cache_tags',
];
}
/**
* Invalidate a tag from the cache tags cache.
*
* @param string $tag
* The tag to invalidate.
*/
function _d8cache_cache_tags_invalidate_cache($tag) {
if (!variable_get('d8cache_use_cache_tags_cache', FALSE)) {
return;
}
cache_clear_all($tag, 'cache_d8cache_cache_tags');
}
/**
* Update the cache with data returned from the database.
*
* @param array $result
* The array of (tag, invalidations) to sync to the database.
*/
function _d8cache_cache_tags_update_cache($result) {
if (!variable_get('d8cache_use_cache_tags_cache', FALSE)) {
return;
}
foreach ($result as $tag => $invalidations) {
cache_set($tag, $invalidations, 'cache_d8cache_cache_tags');
}
}
/**
* Loads cache tag invalidations from the cache.
*
* @param array &$query_tags
* The tags to query in the cache. Will be updated by removing tags
* sucessfully returned from cache.
*
* @return array
* An array keyed by tag with invalidations as value.
*/
function _d8cache_cache_tags_load_from_cache(&$query_tags) {
if (!variable_get('d8cache_use_cache_tags_cache', FALSE)) {
return array();
}
$result = array();
// Load from cache.
$cached = cache_get_multiple($query_tags, 'cache_d8cache_cache_tags');
foreach ($cached as $tag => $item) {
$result[$tag] = $item->data;
}
return $result;
}
Functions
Name | Description |
---|---|
d8cache_ajax_render_alter | Implements hook_ajax_render_alter(). |
d8cache_cache_get_object | Wraps the internal Drupal API with an official API. |
d8cache_cache_tags_get_current_checksum | Returns the sum total of validations for a given set of tags. |
d8cache_cache_tags_is_valid | Returns whether the checksum is valid for the given cache tags. |
d8cache_do_invalidate_cache_tags | Perform cache tag invalidations. Must do outside of a transaction. |
d8cache_emit_cache_max_age | Implements hook_emit_cache_max_age(). |
d8cache_emit_cache_tags | Implements hook_emit_cache_tags(). |
d8cache_flush_caches | Implements hook_flush_caches(). |
d8cache_invalidate_cache_tags | Implements hook_invalidate_cache_tags(). |
d8cache_OLD_invalidate_cache_tags | Backwards compatible version of d8cache_invalidate_cache_tags(). This will automatically be used when the core patch to implement end of transaction callbacks is missing. |
d8cache_process_html | Implements hook_process_html(). |
d8cache_root_transaction_end | Callback to invalidate cache tags at the end of a transaction. |
_d8cache_cache_tags_cache_bins | |
_d8cache_cache_tags_calculate_checksum | Calculates the current checksum for a given set of tags. |
_d8cache_cache_tags_invalidate_cache | Invalidate a tag from the cache tags cache. |
_d8cache_cache_tags_load_from_cache | Loads cache tag invalidations from the cache. |
_d8cache_cache_tags_load_from_db | Loads cache tag invalidations from the database. |
_d8cache_cache_tags_update_cache | Update the cache with data returned from the database. |
_d8cache_has_transaction_callbacks | Check whether the database connection supports root transaction end callbacks. |