Tom Butler's programming blog

PHP: PSR-0: Pretty Shortsighted, Really

A little background for those unaware of what PSR-0 is: There's a self-declared PHP "standards" group called PHP-FIG attempting to push several "standards" throughout the PHP community. The problem I have with this is that their mission statement and their approach are conflicting. Whenever anyone criticises what they're doing their stock response is "It's only for framework authors and it's optional". Their website even says: "If other folks want to adopt what we're doing they are welcome to do so, but that is not the aim." Now, I have no problem with that sentiment and fully support it. The problem occurs when PHP-FIG do attempt to push the standard onto everyone else such as trying to put it into the PHP Core and a lot of campaigning to get people to adopt the "standard".

Even then, I despise the idea of them, or anyone, pushing such a ridiculous "standard" in the first place. I have little interest in debating the politics behind pushing standards or whether small groups of developers trying to make decisions that affect the entire community is good or not, but I do object to the PSR-0 standard itself. My issues are purely practical, PSR-0 reduces flexibility and makes life more difficult for developers. It's self-defeating and as a standard is unfit for purpose. The premise of the standard is: "This is how you must structure your files", which is an absurd starting point.

PSR-0 is a good idea that's been poorly implemented

The idea of a standardised method for including third party libraries in projects is a very good one indeed. This is the underlying problem which PSR-0 attempts to solve. I have full support for people trying to solve this and think that it's a very good idea in itself. The problem is, PSR-0 approaches the problem from the wrong end. Instead of asking "How can I make an autoloader that works with any existing library and allows new libraries to make use of it?" it reframes the question to "How should people structure their libraries to work with this autoloader?". Basically, rather than solving the underlying problem themselves, it passes the buck and forces library authors to solve the problem by structuring their projects in a very specific way.

Surely I'm not the only one who sees why this is problematic? We shouldn't be defining a standardised autoloader to tell library authors how to do things, instead we should be letting library authors optionally provide a way of telling a standardised autoloader how to load its files. This approach has the added benefit of making it so that older existing libraries can be extended, not altered, to become compliant with the standard. As it stands, to make an existing non-PSR-0 compliant library PSR-0 compliant, the entire library must be restructured, and in a lot of cases, have every single file altered to add a namespace to it! To call this approach absurd would be an understatement.

Ok, so perhaps we should only use PSR-0 for new projects and new libraries? If you set out to be PSR-0 compliant then you won't have that problem. This is true and exactly why PHP-FIG are pushing/encouraging everyone else to use their standard, but again it's rather backwards. Choosing your autoloader then using that choice to define how your project is structured can very easily result in a project which isn't structured in the best way for that particular project.

In OOP terms, PSR-0 breaks encapsulation. Code which uses it has implied knowledge about things it shouldn't be concerned with: Directory structures, the implementation details of the autoloader. Storing application configuration along with application logic is a bad idea. It severely limits flexibility by meaning you cannot use different configurations with different logic.

All I'm saying is: Let library authors choose exactly how their projects are structured. This gives people more flexibility in their design, and there's no way to argue against this being a good thing.

As it stands, what actually happens in the real world is that autoloaders are PSR-0 compliant, but then allow a bunch of workarounds to support other non PSR-0 libraries. These workarounds are autoloader specific. Composer uses a classmap, Zend provides several different autoload implementations and Symfony supports PSR-0 as well as a method for loading classes which use the PEAR naming convention.

The current situation is: PSR-0 advocates don't like libraries that don't use PSR-0, and PSR-0 detractors don't like PSR-0 because they want to retain control over the design decisions within their own projects. This is very standoffish and not good for the community as a whole.

What this means is that the "standardised autoloader" that PSR-0 advocates, is far from standard unless you limit the flexibility of your projects' structure by using only other libraries which follow PSR-0. As soon as you set foot outside this very narrow scope, you have to configure the autoloader manually. You'll say "Well, you're not using PSR-0, of course a PSR-0 autoloader won't work!" which is a valid point. However, my question to that is Why make PSR-0 so restrictive in the first place? If the standard defined how autoloaders could be extended, rather than how autoloaders worked, then each library or vendor could provide its own extension to the autoloader. The autoloader itself wouldn't care how the extension worked, all the autoloader would do would be to load the extension!

An example

Rather than sit here and complain that "It's not good enough", I thought I'd show a possible, better, alternative. For people practical like me, it's usually better to show some examples of how things could be done better rather than just saying "here's what's wrong with that approach".

If I was going to define a standard, I'd do several things:

1) Provide an interface so that library authors can tell the autoloader how their classes are loaded, rather than having the autoloader tell the author how his/her classes must be loaded:

interface AutloadRule {
    public function 
load($className);
}

Anyone can implement this and allow their autoloader to use any implementation of its rules. For example, a rule which worked like PSR-0 would look like this:

class PSR0AutloadRule implements AutoloadRule {
    public function 
load($className) {
        
$className ltrim($className'\\');
            
$fileName  '';
            
$namespace '';
            if (
$lastNsPos strrpos($className'\\')) {
            
$namespace substr($className0$lastNsPos);
            
$className substr($className$lastNsPos 1);
            
$fileName  str_replace('\\'DIRECTORY_SEPARATOR$namespace) . DIRECTORY_SEPARATOR;
            }
        
$fileName .= str_replace('_'DIRECTORY_SEPARATOR$className) . '.php';

        if (
file_exists($fileName)) {
            require 
$fileName;
            return 
true;
        }
        else return 
false;

    }

}

2) Provide a standardised mechanism for registering rules with the autoloader. For example, in the library's directory, have a JSON file that loads any autoloaders the library needs.

{
    
"PSR0AutoloadRule""psr0.php"
}

As autoload.json in the library's directory.

The autoloader could work like this:

class Autoloader {
    private 
$rules = array();;

    public function 
__construct() {
        
spl_autoload_register(array($this'load'));
    }


    public function 
registerLibrary($dir) {
        
$autoloaders json_decode(file_get_contents($dir DIRECTORY_SEPARATOR 'autoload.json'));
        foreach (
$autoloaders as $name => $file) {
            require_once 
$dir DIRECTORY_SEPARATOR $file;
            
$this->addRule(new $name);
        }

    }


    public function 
addRule(AutoloadRule $autoloadRule) {
        
$this->rules[] = $rule;
    }

    public function 
load($className) {
        foreach (
$this->rules as $rule) {
            if (
$rule->load($className)) return;
        }
    }

}

Advantages of this approach over PSR-0:

  • The library developer has complete control over how their classes are loaded, they can choose the directory structure and supply a custom autoloader that works only for their library.
  • The autoloader provides a very simple extensible interface, not an autoloading implementation.
  • Existing non standardised libraries can easily be included in the project. An autoload.json would be created and none of the .php files would ever need to be moved or modified. The autoload.json could be shared among developers, unlike PSR-0 where anyone who uses a non-standardised third party library has to manually configure their PSR-0 autoloader to account for it.
  • It can follow PSR-0 rules but it doesn't have to
  • If you wanted to, the autoloader could automatically register libraries from a specific directory based on namespaces but this is application specific and therefore, optional!
  • The library code and directory structure are decoupled from the autoloader, but files can still be autoloaded
  • Autoload configurations can be supplied along with libraries

Disclaimer: I'm not proposing this as a standard, nor am I suggesting that it's the best solution (you could easily achieve similar with multiple calls to spl_autoload_register() but wouldn't have quite so much control over the implementation). I want to show an alternative to PSR-0. The point I'm trying to make is that PSR-0 is ill thought out, too restrictive and is not the answer to the problem at hand. Put the power back in the hands of the developer. My example above is far from perfect, but it's better because it puts the power and design decisions in the hands of the developers, rather than the standards authors.

Conclusion

What PSR-0 tries to achieve is good but its execution is one of the most inflexible and restrictive trends to hit PHP for quite some time. Don't configure your application's structure based on your autoloader, configure your autoloader based on your application's structure.

What's laughable is that over at the PHP-FIG mailing list, there's a post which highlights some of the current implementation restrictions in PSR-0. Their proposed solution? Add another PSR rule as a workaround rather than solving the problem of inflexibility inherent in PSR-0! And what will happen when someone wants to do something else slightly differently in a couple of years? Yet another one? What a joke.

PSR-0 is a bad solution to a good problem. If you take anything from reading this post, remember this: If the standard defined how autoloaders could be extended, rather than how autoloaders worked, then each library or vendor could provide its own extension to the autoloader and everyone would be happy.

In short: PSR-0 answers the question: How can I standardise file structures to fit this autoloader? wheras it should answer the question How can I make an autoloader that loads a given file strucure?