Blog: The End of Autoloading

The Propel Team – 21 March 2011

Autoloading in PHP is a great time saver. It lets you write concise scripts without the knowledge of the exact directory structure of the libraries you use. But with the arrival of namespaces in PHP 5.3, and the influence of Java over new generation PHP frameworks, autoloading is changing. In the near future, explicit autoloading will be ubiquitous, but with none of the advantages of the old style autoloading.

Before Autoloading, There Were File Paths

Before autoloading, every class file had to explicitly declare the path to its dependencies. Source code would look like the following, taken from the PEAR library:

<?php
require_once 'PEAR.php';
require_once 'PEAR/DependencyDB.php';

class PEAR_Registry extends PEAR {
  //...
}

Dependencies appeared clearly at the top of every class. In this code snippet, even if the first use of the PEAR_DependencyDB class is hidden line 328 of the PEAR_Registry class, the dependency is obvious.

In most cases, the path was relative, and the PHP runtime had to rely on the include_path configuration. Performance decreased as the include_path size increased. And the top of many source files was soon littered with require_once calls that harmed readability.

Then Came SPL Autoloading

require_once was notably slow. On servers without a fast disk or an opcode cache, it was better not to use require_once at all. The PHP SPL library’s spl_autoload_register() function then came to a great use. It made it possible to remove require_once calls from source code completely. It made applications faster.

But the greatest benefit was that you could use a class without actually knowing where its source file was in the directory structure. Here is an extract from the “My First Project” tutorial for the symfony framework:

<?php
class postActions extends sfActions
{
  public function executeList()
  {
    $this->posts = PostPeer::doSelect(new Criteria());
  }
}

Here, no require_once at all, even if this class depends on the sfActions, PostPeer, and Criteria classes. Developers could dive into the business logic right away, without spending a single second figuring out where the dependencies were. This was Rapid Application Development at work.

Autoloading Implementations

Implementation of the actual autoloading would vary. Some libraries, like the Propel runtime, included a list of all the classes that could be required, together with the path to the class source. Here is an extract from the Propel class source code:

<?php
class Propel
{
  // ..
  protected static $autoloadMap = array(
    'DBAdapter'      => 'adapter/DBAdapter.php',
    'DBMSSQL'        => 'adapter/DBMSSQL.php',
    'MssqlPropelPDO' => 'adapter/MSSQL/MssqlPropelPDO.php',
    'MssqlDebugPDO'  => 'adapter/MSSQL/MssqlDebugPDO.php',
    // etc.
}

This technique allowed to hide the actual class path, but forced the library developer to update the autoload map each time a new class was introduced. Another technique, used in the symfony framework, used a one-time file iterator that browsed the project directory structure, indexing all .class.php files. Despite the performance impact on the first request, this technique removed the burden to keep an autoload map up-to-date, and worked for classes outside the framework as well.

Even better, the symfony autoloading technique allowed to override framework classes with custom ones. The file iterator browsed the directory structure in a certain order: user directories first, then project directories, then plugin directories, and framework directories last. So the developer could create a custom PostPeer class that would override the other PostPeer class provided by a plugin.

Autoloading was at his top: fast, powerful, concise.

Namespaces Autoloading

The arrival of namespaces in PHP 5.3 forced autoloading techniques to change. An initiative started by some framework authors tried to allow technical interoperability between libraries and the autoloader implementations. Called the “PHP Standards Working Group”, this community agreed that explicit is better than implicit, and that a fully qualified classname would be the relative path to the class source file:

\Doctrine\Common\IsolatedClassLoader
  => /path/to/project/lib/vendor/Doctrine/Common/IsolatedClassLoader.php
\Symfony\Core\Request
  => /path/to/project/lib/vendor/Symfony/Core/Request.php
\Zend\Acl
  => /path/to/project/lib/vendor/Zend/Acl.php
\Zend\Mail\Message
  => /path/to/project/lib/vendor/Zend/Mail/Message.php

Libraries agreeing with the initiative should follow the naming and file structure principles, and provide an autoloading implementation compatible with the example SplClassLoader class. This is the case of most “new-generation” frameworks in 2011. For instance, here is an extract of the new “My First Project” tutorial in Symfony2:

<?php
namespace Application\HelloBundle\Controller;
use Symfony\Framework\WebBundle\Controller;

class HelloController extends Controller
{
  public function indexAction($name)
  {
    $author = new \Application\HelloBundle\Model\Author();
    $author->setFirstName($name);
    $author->save();
    return $this->render('HelloBundle:Hello:index', array('name' => $name, 'author' => $author));
  }
}

There is still no require_once in this code - autoloading is at work. The PHP autoloading looks for a Symfony\Framework\WebBundle\Controller class in the Symfony/Framework/WebBundle/Controller.php file. The file path is no longer relative to the include_path, since the autoloader must be initialized with the base path to the library directory.

A first advantage is that there is no more “first request” penalty on performance. Also, dependencies are explicit again. Lastly, if you want to override a class provided by the framework, alias a custom class using a use and you’re ready to go:

<?php
namespace Application\HelloBundle\Controller;
// use a custom Controller class instead of the framework's Controller
use Application\HelloBundle\Tools\Controller;

class HelloController extends Controller
{
  // same code as before
}

This Is No Longer Rapid Application Development

Doesn’t the initial use in the previous example remind you of something? Right, it’s very similar to the require_once calls of the first example without autoloading:

<?php
// old style
require_once 'Application/HelloBundle/Tools/Controller.php';
// new style
use 'Application\HelloBundle\Tools\Controller';

The added verbosity of the namespace autoloading reduces the ease of use introduced by SPL autoloading in the first place.

The problem is not only about having to write more code. Consider the work to do to use a class from a “new generation” framework:

  1. Parse the framework directory structure, looking for the source file of the class to use
  2. Open the source file, and copy the namespace declaration
  3. Paste the namespace declaration inside a use statement in the custom code.

This copy/paste task happens a lot when working with Symfony2, for instance. This can be somehow improved when you use an IDE with code completion, but you still have to know the fully qualified name of the classes you need. You must know the framework classes by heart to be able to use them. That’s a step backwards in terms of usability when compared to first-generation autoloading, where only knowing the class name was enough.

There Is No Better Way In PHP

Wouldn’t it be great if you could use new generation framework code without knowing where the required dependencies lay on the filesystem? What if you could write a Symfony2 controller like this:

<?php
class HelloController extends Controller
{
  public function indexAction($name)
  {
    $author = new Author();
    $author->setFirstName($name);
    $author->save();
    return $this->render('HelloBundle:Hello:index', array('name' => $name, 'author' => $author));
  }
}

A smart autoloader could catch the call for the Controller class, open a default implementation (in Symfony/Framework/WebBundle/Controller.php), and dynamically alias Symfony\Framework\WebBundle\Controller to Controller. Except that in PHP, use creates aliases at compile time, so that doesn’t work. There is a possibility to implement such an autoloader using eval(), but that’s probably worst than requiring files by hand.

Also, aliasing all classes in a usability layer on top of the framework isn’t possible either. It would defeat the lazy loading of core classes, and fail on duplicate class names (e.g. Symfony\Framework\WebBundle\Command and Symfony\Components\Console\Command\Command).

Unless framework authors change their mind on autoloading, the future of PHP will be verbose.

Solving The Problem

I personally think that the added verbosity slows down the development a lot. Take microframeworks for instance: they give you a way to answer an http request in a fast way but with minimum MVC separation. Compare the code for a “Hello, world” application written using Slim, a microframework without namespace autoloading, and Silex, a microframework using namespace autoloading:

<?php
// Hello world with Slim
require_once 'slim/Slim.php';
Slim::init();
Slim::get('/hello/:name', function($name) {
    Slim::render('hello.php', array('name' => $name));
});
Slim::run();

// Hello world with Silex
require_once 'silex.phar';
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Templating\Engine;
use Symfony\Component\Templating\Loader\FilesystemLoader;
use Silex\Framework;
$framework = new Framework(array(
  'GET /hello/:name' => function($name) {
    $loader = new FilesystemLoader('views/%name%.php');
    $view = new Engine($loader);
    return new Response($view->render(
      'hello', 
      array('name' => $name)
    ));
  }
));
$framework->handle()->send();

In the second example, autoloading comes in the way, and makes things harder.

Developers of new generation frameworks explain that the added verbosity is the price to pay for a better quality code. I’m not sure I’m willing to pay this price. I don’t like to see PHP as the next Java, where the code is great from a CS graduate point of view, but very expensive to write. It makes me want to switch to other languages, where this namespace autoloading discussion never took place, and where rapid application development is still possible.

Take Ruby for instance. It offers a microframework called Sinatra, which makes the “Hello, world” application really concise:

require 'sinatra'
require 'erb'
get '/hello/:name' do |name|
    @name = name
    erb :hello
end

Oh, look, there are require statements in this script. And yet, it’s so fast and easy to use.