Serialized Objects

It sounds pretty obvious. My php warning to you is:

if you serialize a php object, be sure that when you unserialize the object, that the php file which defines the object is loaded in memory.

in complex projects, the ifs-and-whys are often buried deep in the code. The specifics of my saga involve Drupal and panels.

I put all my views and panels in code. In views you do this by implementing hook_views_default_views and in panels you have several different hoosk, one of which is hook_default_panels_page. I also put each view and panel in it's own file, for easier readability and version control.

The first problem is that panels does not cache these defaults, they are read on every single page view. We have a lot of panels. Because reading a hundred include files on every page view is expensive, and since there's extra memory required to parse these files, we started caching our panels_default_panels return in the Drupal cache.

And that introduced the second problem. These panels use the standard panels export, and include references to the panels_context object. Since this object is included in it's own file, and panels makes attempts to not load it if it's not needed, it's possible to get to panels_default_panels, without this file being included.

Then when you try to unserialize the array, you might get (I say might because it only happens on some obscure code path):

Fatal error: panels_context_get_keywords() [function.panels-context-get-keywords]: The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition "panels_context" of the object you are trying to operate on was loaded before unserialize() gets called or provide a autoload() function to load the class definition in ...modules/contrib-patch/panels/includes/plugins.inc on line 1116

The message says it all in nice php-speak, but I didn't get it until Google delivered this article.

I'm not sure if there's some problem in panels, where it's not loading the plugins when it should. Maybe there was a deeper better solution, but my quick fix was simple. I added one line of code to call panels_load_include. Below is the full text of the hook_default_panel_page function.


function rare_default_panel_pages() {
  return _rare_default_panel('panels_page');
}
function _rare_default_panel($type) {
  // get cache if we have it
  static $cache;
  if (!isset($cache[$type])) {
    $data = cache_get('rare_panels_cache');
+   panels_load_include('plugins');
    $cache = unserialize($data->data);
  }

  // load if we do not have a cache or we are on an admin page
  if (!isset($cache[$type]) || strstr($_GET['q'], 'admin/')) {
    // These seem to take up alot of memory.
    ini_set('memory_limit', '512M');
    // Read all of the panels files of a given type.
    $path = drupal_get_path('module', 'rare') . '/panels';
    $files = drupal_system_listing($type .'_.*\.inc$', $path, 'name', 0);
    foreach ($files as $file) {
      require_once("./$file->filename");
      $function = 'rare_'. $file->name;
      $import = $function();
      $cache[$type][$import->name] = $import;
    }
    cache_set('rare_panels_cache', 'cache', serialize($cache), CACHE_TEMPORARY);
  }
  return $cache[$type];
}