X Autoload: "PSR-0-NG" (nowadays "PSR-4")

This is one part in a rather technical series about
X Autoload

PSR-0 and PEAR

Some time ago, the PHP framework interoperability group, consisting of people from different PHP frameworks, published a standard called PSR-0, with policies about how to namespace a class, and where to put the file the class lives in.

The requirements of PSR-0 are described here:
https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md

Most importantly:

  • class \Abc\de_fg\Some_Class
    is stored in file
    $root/Abc/de_fg/Some/Class.php
  • Different $root directories may be used in the same project.
  • A typical PSR-0 implementation will store a map, specifying which PSR-0 root to use for a specific namespace.

The PEAR standard is the equivalent of this, just without the PHP 5.3 namespaces, so it can be used on older PHP versions:

  • class Abc_def_Some_Class
    is stored in file
    $root/Abc/def/Some/Class.php
  • Different $root directories may be used in the same project.

(I did not actually find this on pear.php.net, instead I just believe what the comment docblock on symfony's UniversalClassLoader tells me)

"PSR-0-NG" and "PEAR-NG"

In a discussion about introducing PSR-0 for classes provided by Drupal modules, a few people raised concerns.

It was agreed that a class provided by a Drupal module is to be namespaced as "Drupal\$module_name\Some_Class" or "Drupal\$module_name\SubNamespace\Some_Class".

With PSR-0, this class would have to live in
"$module_dir/lib/Drupal/$module_name/SubNamespace/Some/Class.php".

Some people find this path to be too long, and would rather see the class live in
"$module_dir/lib/SubNamespace/Some/Class.php"
reducing redundancy and making it easier to browse through the module directory.

In that discussion, that newly invented pattern does currently go under the name of "PSR-0-NG", which is quite an arbitrary name, but the one that currently seems to have the most acceptance.

Parallel to PSR-0 and PSR-0-NG, we can define PEAR-NG as the pattern where a class "some_module_Some_Class" may live
in "$module_dir/lib/Some/Class.php", instead of the deeper path "$module_dir/lib/some/module/Some/Class.php".

The discussion reveals several arguments in favour of PSR-0-NG, which we are not going to repeat in this article.

It does also reveal some major drawbacks:

  • It is not compliant with PSR-0, making this another "home-made standard" or "Drupalism", as some people like to say. This being said, the proposal is not totally arbitrary, but rather something everyone with similar requirements would come up with in exactly the same way.
  • Existing PSR-0 autoloaders are unable to load any classes following this pattern. This means:
    • Drupal can not simply reuse an existing PSR-0 autoloader.
    • OOP code from Drupal modules can not as easily be reused in non-Drupal projects, without setting up a dedicated autoloader.

While it can be debated whether PSR-0-NG should become the default for classes provided by Drupal modules, it is certainly an interesting pattern, and something we may want to provide at least optional support for. With an autoloader that does support any of those patterns, we still have free choice what should become the default.

PSR-0 as a subset of PSR-0-NG

One very interesting technical observation is that PSR-0 is actually a subset or special case of PSR-0-NG.

With a PSR-0 autoloader (such as symfony's UniversalClassLoader), we usually register PSR-0 root locations associated with specific namespaces.

Example:
For a namespace "\Drupal\some_module", we register the PSR-0 root location of "sites/all/modules/contrib/some_module/lib", which means that a class "\Drupal\some_module\Some_Class" will be expected in "sites/all/modules/contrib/some_module/lib/Drupal/some_module/Some_Class".

With PSR-0-NG, we could instead register "\Drupal\some_module" to "sites/all/modules/contrib/some_module/lib/Drupal/some_module", with the same result. A class "\Drupal\some_module\Some_Class" would still be expected in the same location.

Putting PSR-0-NG at the heart

This means, every PSR-0-NG autoloader can easily be used as a PSR-0 autoloader, only by adding a bit of registration logic.

The contrary is not the case: A PSR-0 autoloader can not be as easily turned into a PSR-0-NG autoloader.

This is one reason why X Autoload is written mostly from scratch, only loosely based on symfony's class loader.

The heart of the X Autoload class finder is an implementation of PSR-0-NG and PEAR-NG, with additional registration methods that make it feel more like PSR-0 and PEAR.