Drush away unserialize() errors

When you work with the great folks I do every day it comes as little surprise that when the stuff hits the fan and some site is in need of help there will be a legion of folks who jump into round-the-clock action to get things in a better place. While this could be a whole blog post about that alone it's not about that. Rather it's about one of those nagging little problems we came across while working on a site this Thanksgiving "holiday".

The problem is one that many have seen at one point or another:

[26-Nov-2011 22:35:17] PHP Notice:  unserialize() [<a href='function.unserialize'>function.unserialize</a>]: Error at offset 200 of 1100 bytes in /var/www/mysite/includes/bootstrap.inc on line 1104

In fact this problem pops up enough that it has its own page in the Drupal Handbook. The problem is pretty straightforward to understand. Some object was serialized for storage in the database and has become corrupted.

Finding the source of the problem, however, can be challenging. The Handbook page has several examples and more in the comments of some quick hacks that can be done to bootstrap.inc or can be employed with their own database connections to test these serialized values. There are times when neither hacking bootstrap.inc nor connecting to the db directly are practical. Indeed for many using Drush is a good way around both of these. I spent a couple minutes looking and didn't find any scripts out there yet so I cobbled together a quick Drush command to help out here.

<?php

function ckserial_drush_command() {
  $items = array();

  $items['ckserial'] = array(
    'description' => "Checks serialized database columns for proper serialization",
    'arguments' => array(
      'table' => 'The table containing the column to check.',
      'column' => 'The column containing serialized data',
      'key' => 'The column used as a key for reporting',
    ),
    'examples' => array(
      'drush cks table value key' => 'Check serialization of the "value" column of the "table" using "key" as the reference column.',
    ),
    'aliases' => array('cks'),
    'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_DATABASE,
  );

  return $items;
}

function drush_ckserial($table="variable", $column="value", $key="name"){
  $serialized_false = serialize(FALSE);
  $errors = 0;
  $result = drush_db_select($table);
  while ($row = drush_db_fetch_object($result)) {
    $checked[$row->$key] = @unserialize($row->$column);
    if ($checked[$row->$key] === FALSE && $row->$column !== $serialized_false) {
      print "Unserialize Error for variable: ". $row->$key . ' => ' . $checked[$row->$key] . $row->$column . "\n";
      $errors++;
    }
  }
  print sizeof($checked) . " rows were checked in the $table table.\n";
  print $errors . " rows reported errors.\n";
}

Several notes worth making here:

  • It's not yet a fully fleshed out command
  • Yes it should live somewhere other than this blog
  • Yes I will open an issue to get it included in somewhere more useful

In addition to these things it would be useful to have the script know to look in some of the common locations where modern versions of Drupal use serialized database storage. Until all those things get done tossing this in a ckserial.drush.inc file in your .drush directory might come in very handy.

Among the common places to look for problems producing this error in Drupal 6 are these tables:

  • variable
  • users
  • content_node_field
  • content_node_field_instance

To use the command once it's in your file you would do something like:

drush @mysite cks content_node_field_instance widget_settings field_name

Category: 

2 Comments

those pesky serialized arrays

ugh! serialized arrays. The bane of my Drupal existence... but thats another comment in itself. :-P

Thanks, great write-up Josh! Hope you eventually got to some turkey and time with Family!

However, I noticed in your drush command, that you had "prod" before "mysite":

drush @prod.mysite cks content_node_field_instance widget_settings field_name

made me curious. typo? do you know something I dont? visa-versa?

IOW, my understanding is to group based on "mysite." I would have expected your exmple command to look like:

drush @mysite.prod cks content_node_field_instance widget_settings field_name

which allows you to potentially run on all instances of a site. for example if you had these aliases (likely all in one file called mysite.aliases.drushrc.php):

@mysite.prod
@mysite.stage
@mysite.dev
@mysite.local

it then allows you to also simply run the top-level alias

@mysite

On appropriate drush commands to hit "all" sites. Or is Drush moving so fricken fast this has changed in the latest branch?

Can be both :)

Thanks for the question. In short it can be both and is from my point of view on this particular site. That is you can have nested groups so you can have @prod.mysite.prod and @prod.mysite.test if you have an instance (let's say something like Acquia Cloud perhaps for sake of argument). In this case I removed the last .test portion of the name to make it "easier" without thinking that the @prod part would be confusing.

I'll edit the post to make that less confusing.