Lately I have started to make Drupal programming a little bit less tedious by embracing OO side of PHP. Almost immidiatelly I start wondering how exactly class autoload works in Drupal. It took me a couple hours to figure out, but now I DO know.
If you want to know how exactly it works, read "Look under the hood" below. For the rest, these 4 facts are all you need to know:
NOTE: There is rather ugly nuance in the way module gets enabled which can through you a curveball. When module gets enabled, *.module file is loaded before Drupal had a chance to parse all files[] which means that autoload does not know about any classes defined outside of *.module file. Its OK to rely on autoload inside your hooks, but it is NOT OK to rely on autoload in in-line PHP.
// GOOD
function my_module_init() {
$my_object = new MyObject(); // MyObject will be loaded using autoload
}
// BAD. Following 2 lines will not benefit from autoload.
MyObject::doSomething();
$my_object = new MyObject();
Drupal uses spl_autoload_register() to register two autoload functions in _drupal_bootstrap_database() ( includes/bootstrap.inc ). Both of the registered autoload functions work the same way. The only difference is that the first one used to autoload classes and the second one to autoload interfaces. The function that does actual 'loading" is _registry_check_code(). Interesting things are happening around line 2990:
$file = Database::getConnection('default', 'default')->query("SELECT filename FROM {registry} WHERE name = :name AND type = :type", array(
':name' => $name,
':type' => $type,
))
->fetchField();
Table "registry" stores information about all classes and interfaces Drupal was able to discover:
Table "public.registry"
Column | Type | Modifiers
----------+------------------------+----------------------------------------
name | character varying(255) | not null default ''::character varying
type | character varying(9) | not null default ''::character varying
filename | character varying(255) | not null
module | character varying(255) | not null default ''::character varying
weight | integer | not null default 0
Indexes:
"registry_pkey" PRIMARY KEY, btree (name, type)
"registry_hook_idx" btree (type, weight, module)
Next point of interest is function _registry_update() ( includes/registry.inc ). The major purpose of this function is to build a list of files that need to be parsed. Here is the juicy bit:
foreach ($modules as &$module) {
$module->info = unserialize($module->info);
$dir = dirname($module->filename);
// Store the module directory for use in hook_registry_files_alter().
$module->dir = $dir;
if ($module->status) {
// Add files for enabled modules to the registry.
foreach ($module->info['files'] as $file) {
$files["$dir/$file"] = array('module' => $module->name, 'weight' => $module->weight);
}
}
}
Line 10 is why only files explicitly mentioned in *.info are going to be parsed for classes (interfaces).
It is possible to alter the list of files which are going to be parsed for classes using hook_registry_files_alter. One of the most obvious reasons to do this would be to have an ability to add tons of class files without bothering to have them mentioned in *.info file. For example, you may put all your class files in "classes" subfolder in your module and then make hook_regustry_files_alter to look for all files in this subfolder and add them to the list. Another reason could be a desire to add some files to the list that have not been added by a "normal" process. For example, this is exactly what SimpleTest module does.
function simpletest_registry_files_alter(&$files, $modules) {
foreach ($modules as $module) {
// Only add test files for disabled modules, as enabled modules should
// already include any test files they provide.
if (!$module->status) {
$dir = $module->dir;
if (!empty($module->info['files'])) {
foreach ($module->info['files'] as $file) {
if (substr($file, -5) == '.test') {
$files["$dir/$file"] = array('module' => $module->name, 'weight' => $module->weight);
}
}
}
}
}
}
As you can see, it adds *.test files from disabled modules to the list (note to myself, this means that it should be possible to run unit tests from disabled modules)
And finally, the actual parsing: _registry_parse_file (includes/registry.inc). preg_match_all - is all it takes to parse class files:
preg_match_all('/^\s*(?:abstract|final)?\s*(class|interface)\s+([a-zA-Z0-9_]+)/m', $contents, $matches)
Funnily enough, these are really all "smarts" you need to be able to extract class names from any file. As soon as you are not trying to be intentionally dificult :)
class/* this is my class Foo */Foo {
/* Drupal can not see this class */
}
namespace Bar{ class Boom {
/* Drupal can not see this class either*/
}}
Share and enjoy!

Add new comment