Blog: The End of Autoloading
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:
- Parse the framework directory structure, looking for the source file of the class to use
- Open the source file, and copy the
namespace
declaration - 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.