diff --git a/includes/callback.inc b/includes/callback.inc index 75f8d8c..aa466af 100644 --- a/includes/callback.inc +++ b/includes/callback.inc @@ -182,10 +182,10 @@ function js_callback_bootstrap(array &$info) { } } - // At this point in the execution flow it is safe to perform a full - // bootstrap in case of cache misses. + // At this point in the execution flow it is safe to allow our cache handler + // to perform a full bootstrap in case of cache misses. if ($info['cache']) { - JsCache::setFullBootstrap(TRUE); + JsProxyCache::setFullBootstrapAllowed(TRUE); } } } diff --git a/js.api.php b/js.api.php index 604370c..d150b57 100644 --- a/js.api.php +++ b/js.api.php @@ -53,14 +53,23 @@ * instance, loading an entity when entity cache is cold may result in some * data not being loaded and entity cache being corrupt; saving that entity * in subsequent requests may even lead to data loss, if the cache entry was - * not refreshed meanwhile. By default JS will work around the corrupt cache - * issue automatically by performing a full bootstrap whenever a cache miss - * is detected, however the general issue with APIs performing storage - * writes remains. A possible solution is raising the bootstrap level to - * full, although this defeats the purpose of using this module. An - * alternative solution is monitoring the code paths triggered by the - * callback via the "xhprof" integration and make sure all required - * dependencies are actually loaded. + * not refreshed meanwhile. + * Note: by default JS Callback intercepts requests via core's Cache API. + * When a cache miss is detected, it will automatically perform a full + * bootstrap in an attempt to ensure all modules affecting the data to be + * cached are loaded. See "cache" property for more info. + * This does not, however, solve the general issue where a complex callback + * performs a storage write via an API that allows data to be altered via + * hook implementations. In cases like this all dependencies need to be + * explicitly loaded. + * A temporary solution is to raise the bootstrap level to full. However, + * this defeats the entire purpose of using this module. + * A more permanent solution is to monitor the execution path of a callback + * via the "xhprof" integration and ensure all required dependencies are + * added to the callback info. + * - cache: (optional) Flag indicating whether a full bootstrap should be + * performed when detecting a cache miss. Defaults to TRUE. See "bootstrap" + * property for more info. * - includes: (optional) Load additional files from the /includes directory, * without the extension. * - dependencies: (optional) Load additional modules for this callback. @@ -114,8 +123,6 @@ * browser. It is strongly recommended that this is not disabled, otherwise * your site will be susceptible to XSS attacks and be considered * "insecure". - * - cache: (optional) Flag indicating whether a full bootstrap should be - * performed when detecting a cache miss. Defaults to TRUE. */ function hook_js_info() { // Simple callback definition: diff --git a/js.module b/js.module index 56bc5e7..d6bd9f5 100644 --- a/js.module +++ b/js.module @@ -488,14 +488,8 @@ function js_execute_request() { register_shutdown_function('_js_fatal_error_handler'); } - // Initialize the JS cache handler. - $memcache = !empty($conf['cache_default_class']) && $conf['cache_default_class'] === 'MemCacheDrupal'; - module_load_include('inc', 'js', 'includes/cache'); - js_cache_initialize(); - // Memcache requires an additional bootstrap phase to access variables. - if ($memcache) { - js_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES); - } + // Initialize the cache system and our custom handler. + _js_cache_initialize(); // Immediately clone the request method so it cannot be altered any further. static $method; @@ -609,6 +603,41 @@ function js_execute_request() { } /** + * Initializes the cache system and our custom handler. + */ +function _js_cache_initialize() { + global $conf; + + // We do not rely on autoloading as it may trigger a DB bootstrap. + module_load_include('php', 'js', 'src/JsProxyCache'); + + // Collect all the explicitly configured cache bins. + $default_key = JsProxyCache::DEFAULT_BIN_KEY; + $cache_bin_keys = array_values(array_filter(array_keys($conf), function ($key) { + return strpos($key, 'cache_class_') === 0; + })); + $cache_bin_keys[] = $default_key; + + // Save the current configuration and override it to make sure an instance of + // our custom wrapper is instantiated for each configured bin. + $cache_conf = array(); + $default_class = isset($conf[$default_key]) ? $conf[$default_key] : 'DrupalDatabaseCache'; + foreach ($cache_bin_keys as $bin_key) { + $cache_conf[$bin_key] = isset($conf[$bin_key]) ? $conf[$bin_key] : $default_class; + $conf[$bin_key] = 'JsProxyCache'; + } + + // Finally ensure our custom wrappers know which actual cache backend they are + // supposed to use. + JsProxyCache::setConf($cache_conf); + + // Memcache requires an additional bootstrap phase to access variables. + if (!empty($cache_conf[$default_key]) && $cache_conf[$default_key] === 'MemCacheDrupal') { + js_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES); + } +} + +/** * Sends content to the browser via the delivery callback. * * @param mixed $result @@ -788,6 +817,7 @@ function js_get_callback($module = NULL, $callback = NULL, $reset = FALSE) { 'access arguments' => array(), 'access callback' => FALSE, 'bootstrap' => DRUPAL_BOOTSTRAP_DATABASE, + 'cache' => TRUE, // Provide a standard function name to use if none is provided. 'callback function' => $_module . '_js_callback_' . $_callback, 'callback arguments' => array(), @@ -804,7 +834,6 @@ function js_get_callback($module = NULL, $callback = NULL, $reset = FALSE) { 'token' => TRUE, 'xhprof' => FALSE, 'xss' => TRUE, - 'cache' => TRUE, ); } } diff --git a/includes/cache.inc b/src/JsCacheProxy.php similarity index 58% rename from includes/cache.inc rename to src/JsCacheProxy.php index 3672d72..7ed14c2 100644 --- a/includes/cache.inc +++ b/src/JsCacheProxy.php @@ -1,46 +1,14 @@