3

After just having spent two days trying to get to the bottom of a particularly weird bug I thought I'd post up what my question would have been — if I had taken the time to write it — and then post my answer also, just in the hope of helping other people avoid the headache.

PHP Fatal error: Class name must be a valid object or a string in /includes/common.inc on line 7522

This was the starting point. I got this error — along with a typically pleasant WSOD — after creating a new install of my site on a localhost. The strange thing about it was that I had almost exactly the same setup working on another machine. The code was served by GitHub and so was identical, the database was a direct dump from the original site, php was configured as expected and versions were only a 0.0.5 out (5.3.15 and 5.3.10), both Drupals were version 7.9 ... everything should be working.

What made this weirder still was the fact the site would work directly after a drush rr, however, it would break immediately after a drush cc all, or on refresh.

This led my collegue and I traipsing in circles, debugging common.inc and modules.inc trying to find a clue as to what was going on. What we discovered was that after the reset registry the correct entity_info array was built, but after the clear cache, we only got part of the full entity_info array.

Investigating further we found rather ridiculously that the following code — taken from module.inc — could not find particular functions in particular module files that we knew to exist.

foreach ($list as $module) {
  $include_file = isset($hook_info[$hook]['group']) && 
    module_load_include(
      'inc', $module, $module . '.' . $hook_info[$hook]['group']
    );
  // Since module_hook() may needlessly try to load the include file again,
  // function_exists() is used directly here.
  if (function_exists($module . '_' . $hook)) {
    $implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE;
  }
}

This could only mean that at the point this code was being executed certain files that should have been included by now, weren't. What made this even more baffling was that if we called the following outside of any of the caching code, it returned the correct entity_info array.

$entity_info = module_invoke_all('entity_info');

This meant that there wasn't anything wrong with the files, it just seemed to be the caching system. To cut a rather long story short, because we didn't have anything modifying the core drupal caching system, we decided to resort to the good old trial and error disable all modules and enable one by one. Great.

This took some time but we managed to whittle everything down to one bespoke module, which we then trial and errored function by function call. Until we got the culprit:

/**
 * Helper function to redirect an actual submit to the ajax handler
 */
function microsites__admin_settings_submit(&$form, &$form_state) {
  return microsites__admin_settings__ajax_button(&$form, &$form_state);
}

So I guess my question — apart from being, what can cause PHP Fatal error: Class name must be a valid object or a string error — would be what out of the above function could cause such a disastrous error, and in such a weird manner?

2 Answers 2

4

For those who have been using PHP a while, it should be obvious what is causing the issue, although what wont be obvious is why Drupal didn't make a song and dance about it.

Basically the cause of the WSOD were the two ampersands denoting call time pass by reference. Something that has been deprecated in PHP for a while. The reason why they were there at all was just a copy and paste typo.

return microsites__admin_settings__ajax_button(&$form, &$form_state);

The above should have been:

return microsites__admin_settings__ajax_button($form, $form_state);

With the above change made, everything magically started working again.

Now I might get some people assuming that we just hadn't been keeping track of our errors, but we had as much logging enabled as possible... at no point did either PHP or Drupal complain about the call time pass by reference. Instead what Drupal seemed to do — and only after clearing cache I might add — was fail to include other files once it had hit this illegal / deprecated syntax, which then went on to create malformed entity_info arrays.

I have since tested this on other files, and all it takes is to add a call time pass by reference and it breaks Drupal, at least for all my localhost setups anyway... that is, unless the following has been specified in the php.ini:

allow_call_time_pass_reference = On

If anyone can ascertain as to why the silence (considering all other PHP errors and warnings are shown), why it broke other files loading, and why the problem only affected when running after a cleared cache, I'd be interested to hear.

Maybe upgrading to 7.22 would fix a few of the above... but in future I'll make sure I double check coherence between my entire php.ini(s) I think; rather than spend a few days trying to bash through a brick wall using only a toothpick.

update

Just to answer Clive's question, this was the output in the apache error log after I've just tested the same problem again:

[Thu Apr 11 11:15:29 2013] [notice] Digest: generating secret for digest authentication ...
[Thu Apr 11 11:15:29 2013] [notice] Digest: done
[Thu Apr 11 11:15:29 2013] [notice] Apache/2.2.17 (Unix) mod_ssl/2.2.17 OpenSSL/0.9.8r DAV/2 PHP/5.3.6 configured -- resuming normal operations
2
  • Regarding the silence - are there any errors reported in the Apache log when the exception occurs?
    – Clive
    Commented Apr 10, 2013 at 17:25
  • @Clive - no unfortunately nothing reported there either :S I guess it'll be something either hidden away in Drupal or in my php/apache setup.
    – Pebbl
    Commented Apr 11, 2013 at 10:25
2

This is the tip of the iceberg on this problem. (the PHP Fatal error: Class name must be a valid object or a string in /includes/common.inc on line 7522).

in 7.28, at line 7855, 6 =

    $class = $type_info['controller class'];
    $controllers[$entity_type] = new $class($entity_type);  

The issue stems from, for whatever reason, not being able to obtain a $class value from the $type_info array by executing module_invoke_all('entity_info'). Certainly a call time pass by reference could do it, but that would normally be caught. The actual problem is in /includes/common.inc at 7644.

  if (empty($entity_info)) {
    if ($cache = cache_get("entity_info:$langcode")) {
      $entity_info = $cache->data;
    }
    else {
      $entity_info = module_invoke_all('entity_info');
      // Merge in default values.
      foreach ($entity_info as $name => $data) {
        $entity_info[$name] += array(
          'fieldable' => FALSE,
          'controller class' => 'DrupalDefaultEntityController',
          'static cache' => TRUE,
          'field cache' => TRUE,
          'load hook' => $name . '_load',
          'bundles' => array(),
          'view modes' => array(),
          'entity keys' => array(),
          'translation' => array(),
        );
        $entity_info[$name]['entity keys'] += array(
          'revision' => '',
          'bundle' => '',
        );
        foreach ($entity_info[$name]['view modes'] as $view_mode => $view_mode_info) {
          $entity_info[$name]['view modes'][$view_mode] += array(
            'custom settings' => FALSE,
          );
        }
        // If no bundle key is provided, assume a single bundle, named after
        // the entity type.
        if (empty($entity_info[$name]['entity keys']['bundle']) && empty($entity_info[$name]['bundles'])) {
          $entity_info[$name]['bundles'] = array($name => array('label' => $entity_info[$name]['label']));
        }
        // Prepare entity schema fields SQL info for
        // DrupalEntityControllerInterface::buildQuery().
        if (isset($entity_info[$name]['base table'])) {
          $entity_info[$name]['schema_fields_sql']['base table'] = drupal_schema_fields_sql($entity_info[$name]['base table']);
          if (isset($entity_info[$name]['revision table'])) {
            $entity_info[$name]['schema_fields_sql']['revision table'] = drupal_schema_fields_sql($entity_info[$name]['revision table']);
          }
        }
      }
      // Let other modules alter the entity info.
      drupal_alter('entity_info', $entity_info);
      cache_set("entity_info:$langcode", $entity_info);
    }
  }

Notice that if the condition is true in the if at

if ($cache = cache_get("entity_info:$langcode")) {
      $entity_info = $cache->data;
}

then the module_invoke_all('entity_info') is never executed. It just happens that the condition is always met after some other processing is done to create the cache. That means that there is no way to get the entity_info set up --- in my case there was no way to obtain the 'user' entity info, causing the original error above. I made the 'else' one level higher (took out the 'else {}') for testing, and everything worked as it should to clear the error.

I believe that the real bug is that the checking of cache is less than adequate, and that a check on the existence of the entity_info about the specific entity being searched for is needed. In fact, it may be justifiable to also search for the element ['controller class'] for that entity in the entity_info stored in cache. D

1
  • Is there a Drupal core issue for this?
    – joelpittet
    Commented Feb 27, 2015 at 18:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.