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
namespacedeclaration - Paste the namespace declaration inside a
usestatement 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
endOh, look, there are require statements in this script. And yet, it’s so fast and easy to use.
18 comments
require_once __DIR__.'/silex.phar';
use Silex\Framework;
$app = new Framework();
$app->get('/hello/{name}', function($name) {
return "Hello $name";
});
$app->run();
I think that this is a good example of how to get something that should be simple (and it's not) to what it should have been from the beginning.
In the end, I think that it's a matter of the weight of your framework of choice in your shoulders what it really matters. In this version of Silex you don't really need to use the DI container, nor namespaces if you don't want to and it has SF2 under the hood.
If you do TDD and take care about SOLID principles, you will end with some degree of DI in your bussiness logic. If so, I think that namespaces and SPL autoloading play a good role in my projects but I'm skeptic about what's being proposed lately with DI containers everywhere and for everything.
I'm just eager to see what happens in the end with everything (especially about Propel of which I'm a really happy user since SF 0.6 alpha) around this matter. In the meantime, I see those microframeworks very appealing...
The expected approach, as in Java, Scala, and many others would be to simply issue a "use Silex\*" and that should load all the classes/defines under that level.
[code lang="php"]
require_once 'path/to/Application/HelloBundle/Tools/Controller.php';
use Application\HelloBundle\Tools\Controller as Controller;
[/code]
Then, in your controller class, you just need to add:
[code lang="php"]
require 'Controller';
[/code]
Of course, you can use a command to generate these simple require scripts. Notice that they don't use autoloading: they are even more explicit than the Symfony2 autoloading technique!
The command generating require scripts (let's call that "directory autoloading") can use precedence rules, just like the symfony1 autoloader, to allow overriding of core classes.
Don't leave PHP for Ruby, there is still a lot to do there!
@jiantin: That's smart. You should suggest that to the PHP Standards Working Group.
But I currently have the same opinion about PHP becoming "a second Java" in terms of verbosity.
Even Fabien Potencier nearly gave up debugging his hello word demo in symfony live when it crashed for a simple virtualhost fault <_><
@jlantin: A very interesting idea, one which I'm not smart enough to see all the way through. I'd love to see that in practice - something like that could be something built onto one of the more "RAD" Symfony2 distributions.
Apart from the namespace issue that Francois is talking about (I'll cover that momentarily), I think Symfony2 is going to be just good for rapid development as symfony1, but with less wtf moments. The reasons for this are flexibility (the sky is the limit for building useful tools), coupled with a very passionate and active community that is eager to build these things and think of more ways to make Symfony2 special. The use of the FrameworkExtraBundle is an example of this: you get symfony1-style actions *plus* route configuration all in one.
With the addition of a console task for generating controllers (and especially with the FrameworkExtraBundle), an entire "page" can be created without writing any boilerplate code or worrying about namespaces. The point is that you can pick your extremes (be totally explicit, or include magic and use generation).
The only real difference is that class names are longer in Symfony2 (that's really it!). In symfony1, you still need to remember class names to create things manually (e.g. sfActions).
From a practical standpoint, I *do* think that we should help our users. First, for RAD, we should include code generation everywhere it makes sense. This puts us pretty much on par with a framework like symfony1, where you could create an entire application without ever manually creating a class. Second, we should have a namespace cheetsheet or some other handy method to give people easy access to common namespaces. This should actually put us ahead.
The most constructive thing we can do is find places where we can improve developer workflow and then work together to find clever solutions. This post certainly highlights one such issue, which I think we can certainly overcome. To help all developers, we should ask "how could I do X with as little work as possible", and then build the tools to make that possible.
Cheers!
Although I agree with many of the points here, I find the explicit loading -- whether through require_once *or* namespace-based autoloading -- has an advantage that's not mentioned, and that is the ability of someone who doesn't know the framework very well to understand what's going on; to read the code more easily.
I found myself grepping the entire framework library many times when trying to learn about Symfony 1.4, because I'd be trying to understand some example code, and that would use a class from a plugin, which would use a class from the core, which would use another class from the core, which would inherit from another class in the core...
And every time I needed to make the "jump" from one bit of code to the next it was difficult and slow to find the next file I needed to read. Because it could be *anywhere*, and it was difficult for a beginner to predict where to find the next class along the chain of understanding.
The "magical" autoloading is good in many ways, but when it comes to reading the code, I prefer the namespace-based autoloading of Symfony 2, because as a beginner I'm not struggling so much to figure out where things are...
Any decent IDE would do a "jump to declaration".
Namespaces had been introduced for two main reasons (as php.net states):
- Name collisions
- Ability to alias (or shorten) Extra_Long_Names designed to alleviate the first problem
But, and that's a not a very good programmer point of view, they've introduced much more problems than solutions.
I think that "sooner or later you're going to realize, just as I did, that there's a difference between knowing the path" and just writing a long class name! And the latter is better.
And last but not least, giving the ability to use the same name to similar classes is not a good thing: it just bring more doubts and difficulties.
Code will not be more readable, because you will need to know which class you used, based on it's namespace, to initialize a variable. So, being lazy with names will force you to recall all the namespace to understand what that variable is supposed to do.
I must admit that in my first symfony2 attempts, when I realized that the class namespace MUST be the same as the path, I thought: then where is the benefit?
I think that namespaces are a new feature that doesn't add a real advantage except avoiding collision. And this almost never happened to me...
I did once try to use Eclipse, but it was a couple of years ago, and it took an hour to get it installed and then it exhausted the memory on my Windows box as soon as it was loaded, and promptly crashed. It's possible that this one experience has put me off big IDEs for too long :)
(sorry for the off topic)
Anyway ! With code generation, the problem will be, for me, resolved.
On the other hand, namespaces solve several issues. Like require_once calls, they document dependencies; you have a predictable location in the class file to look in order to determine what it'll be using. At the same time, because "use" statements are essentially hints to the compiler, you get the performance gain of just-in-time resolution only when executing a statement that uses a given class. Finally, there's an important point you referenced but glossed over: if you want to provide a substitute implementation, you simply change the import statement to reference the class you want to use, and alias it.
Are import statements verbose? Perhaps; I find it rare, however, that I use more than a handful of other components in any given class file, which means my import statements do not contribute meaningfully to code count. Do they help in maintenance and flexibility? Absolutely.