Unsupported operand types” in Drupal 6.x

I recently (finally) updated my blog to Drupal 6, but I was having all sorts of trouble running cron. Instead of doing all the chrony magical stuff, it kept saying things like this:

Fatal error: Unsupported operand types in [...]/drupal/includes/common.inc on line 1376
Fatal error: Unsupported operand types in [...]/drupal/includes/common.inc on line 1546

If you run into either of these errors, you’ve found the one of the joys of a rapidly changing api! As of Drupal 6.x, Drupal has changed the arguments for the l() and url() functions. These functions used to have a bunch of arguments for things like ‘absolute’, ‘query’, ‘fragment’, and so forth. The problem was when you had a fragment but no query string, or you just wanted to declare a url as absolute. You had to add a whole bunch of null arguments to get to the one you actually wanted to set. So Drupal made a wise move and combined all the options into a single argument, fittingly named $options.

Check this out

<?php

// The old way to wrap an image in a link.
l('<img src="foo.png"/>', 'node/1', array(), null, null, null, TRUE);

// And the new way
l('<img src="foo.png"/>', 'node/1', array('html' => TRUE));

See how much easier the new way is?

A problem arises

The problem comes when the new functions are handed arguments intended for the old ones. I ran into it in a block and a node, both of which contained PHP snippets. Since I was upgrading the site from Drupal 5, these snippets expected the old function. And it turns out that the new function is none too happy when you pass it the wrong arguments.

And here are three options to get around it

Depending on how much mucking around in Drupal code you feel like doing, there are several ways to fix this problem.

First, you can just make things work

Here’s what it’ll take to make the errors go away. I wouldn’t suggest using this method unless you’re bypassing an error in a module you didn’t write, and you don’t really feel like hacking around in it to fix things. Simply drop these two snippets into drupal/includes/common.inc.

<?php

  /**
   * Add this code right after the line that says:
   *
   * `function url($path = NULL, $options = array()) {`
   *
   * (currently on line 1368)
   */
  // Convert arguments and move on...
  if (!is_array($options)) {
    $options = array();
    $args = func_get_args();
    if (isset($args[1])) $options['query'] = $args[1];
    if (isset($args[2])) $options['fragment'] = $args[2];
    if (isset($args[3])) $options['absolute'] = $args[3];
  }
<?php

  /**
   * And drop this surprisingly similar code right
   * after the line that says:
   *
   * `function l($text, $path, $options = array()) {`
   *
   * (currently on line 1541)
   */
  // Convert arguments and move on...
  $args = func_get_args();
  if (count($args) > 3) {
    $options = array();
    if (isset($args[2])) $options['attributes'] = $args[2];
    if (isset($args[3])) $options['query'] = $args[3];
    if (isset($args[4])) $options['fragment'] = $args[4];
    if (isset($args[5])) $options['absolute'] = $args[5];
    if (isset($args[6])) $options['html'] = $args[6];
  }

Your code should work exactly as intended. Note that this method is a dirty hack, but will get the job done (i.e. treat the symptoms). Don’t come crying to me when something breaks later, because I’m warning you now.

Alternatively, you could add some logging.

This way you at least know what’s happening. Just after the last of those if (isset($args[6])) lines, add a call to trigger_error().

trigger_error("Deprecated url formatter call");

This will be logged to the screen, to your server’s error log, or to your database (watchdog), depending on your site configuration. Adding logging to your hack is preferred, because that way you know how often it’s happening. If things get out of hand, you can always go for door number three:

Find and fix the source

For this we’ll use a tool called debug_print_backtrace(). It’s a super-rad built-in PHP debugging tool that tells you exactly what call path led your app to this unfortunate end. You can wade through the calls and (probably) figure out who is to blame for your predicament. This is how I found my problems. Your mileage may vary.

Add this (instead of the argument conversion code):

<?php

  // Die.
  $args = func_get_args();
  if (!is_array($options) || count($args) > 3) {
    echo "<pre>";
    print_r($args);
    echo "</pre>";
    echo "<hr/>";
    echo "<pre>";
    debug_print_backtrace();
    die();
  }

The first chunk of output is your link builder arguments. The second, and much larger, piece is your backtrace. Your culprit is often on line #1 or #2. Mine was a call to eval (line #1), which happened because of a node with php_filter enabled (line #5)… And the arguments to line #5 were kind enough to give me a node id. Sweet! Mystery solved!

I’m not going to lie, the debug backtrace is usually a bit ugly. And it might take you a bit to find the culprit. But this is the real way to solve your unsupported operand error.

Good luck!