Autoloading for legacy, non-framework projects
Update
It’s 2013. Composer solved outloading. Even for legacy projects classmaps solve all problems.
Go use that.
No seriously. Stop reading. You don’t need anything except composer classmaps.
Old blog post:
Using an autoloader seems to be the cool thing to do at the moment.
Everyone is doing it. There is even a proposal for the one true autoloader.
Motivation
So why use an autoloader?
The first argument usually is “easy of use”. It can get pretty annoying when you have to clutter your whole application with “require this file here and that file over there” statements. Especially once you loose track of what is actually already loaded. You can hand the Task to PHP when using require_once but that will result in some not so fast realpath()
calls and some, mostly negligible, overhead in php.
The main motivation for autoloading usually is “getting rid of all the require statements and the problems resulting from forgetting one at one point where you don’t notice it because on your machine it worked.
Legacy- or non-framework applications
If you are developing using a framework then chances are you will be using the autoloader provided by that framework. Maybe, for whatever reasons, you choose not to do so but for the most part this will because you are dealing with an older application or building something from components.
In my case there was a old project containing over 1500 Classes and having no classname to filesystem mapping
.
A decision that makes it easier to move stuff around but makes autoloading a little tricky. While everything worked fine (in that regard at least) for many years the idea was to make life a little easier for developers while improving performance at the same time. Not only did the bootstrap pull in over 270 classes, those files also contained lots and lots of defines so that, using apc, an average bootstrap took over 100ms. Cutting that down without much hassle seemed like a nice idea.
As it turned out it was. After implementing an autoload solution and removing about 20% of the requires in the codebase (going through everything right from the start would have taken to long) the bootstrap went down to only pulling in 36 classes and a normal page didn’t use many more than those. For many short running cli scripts the benefit was even greater.
Solution
Well.. you could use an autoloader that scans the file system on request and loads a file called MyClassname.php
when you want the MyClassname
class but that just doesn’t seem like a good idea to do that every time a class is needed.
Since there is no way of magically knowing where a class is the idea was to build a mapping "classname" => "file"
and thanks to the PHP Autoload Builder there is a tool that generates that mapping for you and stores it in a file.
Just to show how this is done let’s create a very simple project.
mkdir src/ src/stuff src/otherStuff src/config
and create some classes
echo '<?php class StuffClass {} ' > src/stuff/StuffClass.php
echo '<?php class OtherStuffClass {} ' > src/otherStuff/OtherStuffClass.php
Now install phpab:
sudo pear channel-discover pear.netpirates.net
sudo pear channel-discover components.ez.no
sudo pear install theseer/Autoload
and let’s generate the autoload file and put it in src/config (Note: many projects i’ve seen put that file directly into the src folder, i just want to show that it doesn’t matter where you put it)
phpab -b src/config/ -o src/config/autoload.php src/
and it has created a nice file for us that we put in the scm and just need to require in our bootstrap code
<?php // this is an autogenerated file - do not edit (created Tue, 08 Mar 2011 22:33:16 +0100)
spl_autoload_register(
function($class) {
static $classes = null;
if ($classes === null) {
$classes = array(
'otherstuffclass' => '/../otherStuff/OtherStuffClass.php',
'stuffclass' => '/../stuff/StuffClass.php'
);
}
$cn = strtolower($class);
if (isset($classes[$cn])) {
require __DIR__ . $classes[$cn];
}
}
);
What we did while migrating away from the require statements is to use a different template file for phpab (-t option) and use a “require_once” statement just to make sure we never run into a “already defined class” problem. When you are using autoload from scratch you don’t need this but for that project it was possible that:
- Class A gets loaded from the autoload
- Class B get loaded from the autoload
- Class B has a require_once statement pulling in ClassA.php
and we wanted to avoid running into those troubles. PHPAB will work for PHP 5.2 and PHP 5.3 (while the generation only works with 5.3 the generated code and be 5.2 compatible (-c option) and of course it also works with interfaces and namespaces.
Working with PHPAB
To add newly created classes to the mapping there are 3 options i’ve played around with
Add them by hand
This is quickest way to add a newly created class as it usually involves copying one line and changing the path and classname.
Rerunning the tool
Works well. I’d advice you to wrap the call in a createAutoload.sh or put it in your buildscript so you don’t have to remember the parameters. This will also clean up all deleted classes.
and if that is too much hassle for you
You could even put a little code in your autoload template that just automatically reruns the autoloader when a class isn’t found. So after adding a new class and using it somewhere in your code you get a “class not found” error, hit F5 again (or rerun the cli command) and it just works.