We need to disable caching for pages (that are normally cached), based on the presence of certain things in anonymous users' $_SESSION (similar to "shopping cart" type selection data).

I see a thread discussing exactly this for D7, but can't find anything for D6, so thought I would start this thread.

I don't see any good way of accomplishing this. At first glance, it seems like you should just be able to use hook_boot() (which is called even for cached pages) to set $GLOBALS['conf']['cache'] = FALSE. However, this does not work, because _drupal_bootstrap() loads the cached page before it invokes hook_boot():

    case DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE:
      // Initialize configuration variables, using values from settings.php if available.
      $conf = variable_init(isset($conf) ? $conf : array());
      $cache_mode = variable_get('cache', CACHE_DISABLED);
      // Get the page from the cache.
      $cache = $cache_mode == CACHE_DISABLED ? '' : page_get_cache();
      // If the skipping of the bootstrap hooks is not enforced, call hook_boot.
      if (!$cache || $cache_mode != CACHE_AGGRESSIVE) {
        // Load module handling.
        require_once './includes/module.inc';
        bootstrap_invoke_all('boot');
      }
      // If there is a cached page, display it.
      if ($cache) {
        drupal_page_cache_header($cache);
        // If the skipping of the bootstrap hooks is not enforced, call hook_exit.
        if ($cache_mode != CACHE_AGGRESSIVE) {
          bootstrap_invoke_all('exit');
        }
        // We are done.
        exit;
      }
      // Prepare for non-cached page workflow.
      drupal_page_header();
      break;

Has anyone found a way to make this work?

One idea I had, was to try invoking the rest of the full bootstrap process ourselves when we need to bypass the cache, by calling drupal_bootstrap( DRUPAL_BOOTSTRAP_FULL ) directly from hook_boot(), and then never returning. Looks like I should also call drupal_page_header() in that case, to "complete" the steps in DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE above.

Am I crazy? Has anyone else mucked around with the bootstrap process like this? Any unintended consequences I should be wary of?

Comments

brian_c’s picture

I seem to have gotten it working. If you're going to hijack the bootstrap process like this to bypass the standard caching system, you also have to replicate the rest of index.php, since you'll never be returning to that. You also need to make sure your module has a higher weight than any other modules which use hook_boot(), to ensure all the other hook_boot()'s get a chance to run. You can adjust module weights manually in the database, or use the Utility module (http://drupal.org/project/util).

Anyway, here is the code we have for hook_boot() now:


function MODULENAME_boot(){

  $bypass_cache = false;


  // do various checks here to see if we want to bypass the cache, if so set $bypass_cache = true;
  // eg...
  if( some_condition || another_condition ){
    $bypass_cache = true;
  }


  if( $bypass_cache ){
    // we've already passed the cache check, but set this just in case something else checks it
    $GLOBALS['conf']['cache'] = FALSE;
  
    // Complete remaining DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE steps from from _drupal_bootstrap()
    // Prepare for non-cached page workflow.
    require_once variable_get('lock_inc', './includes/lock.inc');
    lock_init();
    drupal_page_header();

    // now execute remaining bootstrap phases
    drupal_bootstrap( DRUPAL_BOOTSTRAP_FULL );
    
    // now complete remaining steps from index.php
    
    // execute page, get output
    $return = menu_execute_active_handler();    

    // Menu status constants are integers; page content is a string.
    if (is_int($return)) {
      switch ($return) {
        case MENU_NOT_FOUND:
          drupal_not_found();
          break;
        case MENU_ACCESS_DENIED:
          drupal_access_denied();
          break;
        case MENU_SITE_OFFLINE:
          drupal_site_offline();
          break;
      }
    }
    elseif (isset($return)) {
      // Print any value (including an empty string) except NULL or undefined:
      print theme('page', $return);
    }

    // finish page output
    drupal_page_footer();
    
    // we're done
    exit;
  }
}

It's working great, we can turn off caching conditionally at runtime for any reason we want. One caveat of course is that no modules (other than ones implementing hook_boot) are loaded yet when this code executes, but you do have access to the user's session and the database by this point, so you can really check anything you want.

Will have to do additional testing to see if there's any side effects, but looking pretty solid so far.

Mixologic’s picture

If you want to simply keep the pages out of the cache,

Take a look at:

http://drupal.org/project/cacheexclude

If you *sometimes* want pages cached for anonymous users, and *sometimes* do not, then you will have to resort to what is described above.

brian_c’s picture

Yup CacheExclude is great if certain path(s) should always be kept out of the cache.

The problem is when you want pages to dynamically switch their caching behaviour at runtime. This is normally impossible due to the sequence of events in Drupal's bootstrap process; cached pages are loaded before hook_boot() has a chance to turn off caching. (You could do it from settings.php, but that's a pretty fugly workaround.)

The above approach allows modules to disable caching from hook_boot(), and have that actually work.

I'm actually trying to get this submitted as a module called Dynamic Cache, you can see the thread here: http://drupal.org/node/916552. Perhaps you could help test it?

doublejosh’s picture

For what it's worth, I'm using this successfully.

brian_c’s picture

There is now a sandbox version of this module available here: http://drupal.org/sandbox/brian_c/1413374

brian_c’s picture

This is now a full module, with working implementations for both D6 and D7: http://drupal.org/project/dynamic_cache