Efficient autoloaders for PHP
Class autoloading is one of more commonly used features in PHP 5. It simplifies the application development, freeing us from manual dependency management. On the other hand, many projects developed custom class naming standards and autoloaders that were incompatible one with another. Currently more and more programmers notice the benefits that come from accepting a single, widely implemented convention. In this article we are going to introduce Open Power Autoloader, a collection of universal, fast autoloaders for PHP 5.3 compatible with PSR-0 class naming convention adapted by such projects, as Zend Framework or Symfony 2.
Many programmers tend to invent custom, project-specific class naming systems that are not used anywhere else, which forces them to write a custom autoloader for them as well. This approach causes lots of problems, when we try to link several pieces of code and libraries into a single system. The only way is to register all the autoloaders and hope that they will not collide one with another. The community noticed the problem after releasing PHP 5.3 which introduced the concept of namespaces. It was obvious that unless we want a complete chaos, some basic common conventions must be defined and widely accepted. Such a convention, called PSR-0 has been finally defined, and implemented in a group of the biggest PHP projects, such as Doctrine 2, Symfony 2 and Zend Framework. It also allowed to create the first universal autoloaders, capable of handling any number of libraries in a single instance.
Class autoloaders have a critical impact on the application performance. In modern web applications, the number of classes loaded in a single request can exceed 100. For each of them, the autoloading function must be called which translates the class name to a file path and attempts to load it. Here is an example. A couple of months ago I was developing a web application with PHP 5.3. One day I started profiling the code and I was surprised with the first results. It turned out that processing the class names in the autoloader took more than 40% of the overall script execution time. APC did not help here, because despite the opcode caching, the autoloading function still must be called in order to see, what opcode should be actually loaded. This is why choosing a proper autoloader for our project is so important.
1. Open Power Autoloader ↑
Currently, there is a couple of universal class loaders available. We have
SplClassLoader which is the default implementation, the Universal Class Loader from Symfony 2, and Zend_Loader from Zend Framework. However, they are either parts of bigger projects, or do not offer a satisfactory performance with the bigger number of namespaces. Open Power Autoloader is a collection of universal class loaders compatible with PSR-0 designed for performance. The general idea behind them comes from the fact, that if we attempt to build an autoloader for all the possible environments and situations, it will be extremely slow. Instead, we can have a group of smaller, specialized autoloaders that should be choosed according to the needs of the concrete environment. There are several rationales for this approach:
- The autoloader is configured and installed once, in the very beginning of the script. The rest of the application does not even have to be aware of it.
- Different environments have different needs that cannot be easily combined in a single loader.
- Simple autoloader improves the performance.
- The autoloader itself must be loaded manually, so the programmer should not be forced to write lots of require statements.
- The autoloader API does not have to be complex. All we need is to define the supported namespaces and paths to the file locations.
Currently, the project provides three autoloaders:
Opl\Autoloader\GenericLoader- a standard autoloader that translates the class names into file paths dynamically. It is good for the development and testing environments, where the class hierarchy can be changed very often. It is also suitable for low-volume websites.
Opl\Autoloader\ClassMapLoader- an autoloader that uses the pregenerated class map loaded from an external file. It provides the biggest performance, but it requires to regenerate the map manually every time the class hierarchy is changed. It is suitable for production environments.
Opl\Autoloader\PHARLoader- a variant of
ClassMapLoaderfor self-contained web and console applications packed in PHAR archives.
They are accompanied by a class map building library and a console command for Symfony 2 Command-Line Interface that does the same.
2. Installation ↑
Open Power Autoloader can be installed in two ways. In the first one, we simply visit the Download page at Invenzzia.org website, download the newest possible version and copy the contents of the
/src directory to our project directory tree. We can also use PEAR:
pear channel-discover pear.invenzzia.org pear install invenzzia/OPL_Autoloader
In order to use the CLI command, we must also install the Symfony 2 Console component.
3. Usage ↑
Open Power Autoloader provides universal class loaders that can handle every PHP project that follows the PSR-0 naming convention, for example Doctrine 2, Symfony 2 or Zend Framework 2. Any number of libraries can be handled by a single autoloader instance. The autoloader setup is very simple. Below, we can see a sample configuration for
require('../src/Opl/Autoloader/GenericLoader.php'); $loader = new Opl\Autoloader\GenericLoader('../src/'); $loader->addNamespace('Opl'); $loader->addNamespace('Symfony'); $loader->addNamespace('Doctrine'); $loader->addNamespace('Application', '../app/'); $loader->register();
All we have to do is to add all the namespaces we want to handle and define paths to their file locations (if they are different than the default one). Optionally, we can also choose the file extension as the third argument of
addNamespace() method. By default, the autoloader assumes that the projects use namespaces, but it can be also reconfigured to handle the legacy code written for PHP 5.2:
<?php require('../src/Opl/Autoloader/GenericLoader.php'); $loader = new Opl\Autoloader\GenericLoader('../src/'); $loader->addNamespace('Opl'); $loader->addNamespace('Symfony'); $loader->addNamespace('Doctrine'); $loader->addNamespace('Application', '../app/'); $loader->register(); $legacyLoader = new Opl\Autoloader\GenericLoader('../src/', '_'); $legacyLoader->addNamespace('Opt'); $legacyLoader->addNamespace('Zend'); $legacyLoader->register();
The second argument of the constructor allows us to select the namespace separator, which is set to
\ by default. By replacing it with an underscore, we can handle older code, such as Zend Framework 1.x or Open Power Template 2.1.
For bigger websites, their production environment needs a faster autoloader. When we install the application on the production server, we can change the autoloader to
ClassMapLoader which uses a precomputed class map loaded into the memory:
<?php require('../src/Opl/Autoloader/ClassMapLoader.php'); $loader = new Opl\Autoloader\ClassMapLoader('../src/', '../data/classMap.txt'); $loader->addNamespace('Opl'); $loader->addNamespace('Symfony'); $loader->addNamespace('Doctrine'); $loader->addNamespace('Opt'); $loader->addNamespace('Zend'); $loader->addNamespace('Application', '../app/'); $loader->register();
Notice that this autoloader sees no difference between the libraries that do use PHP 5.3 namespaces and the libraries that do not. Everything we need is stored in a map, so we do not have to worry about it here. As a second argument of the constructor, we must provide the path to the class map file, but how do we generate it? For this task, we must use
ClassMapBuilder. It is a directory scanner that tokenizes each encountered PHP file and scans its source for namespace, class and interface definitions. A single file must contain at most one class or interface in order to make it work properly. An extra support for PHP 5.4 traits, which are also the subject of autoloading, is already being implemented.
<?php require('../src/Opl/Autoloader/ClassMapBuilder.php'); $builder = new Opl\Autoloader\ClassMapBuilder(); $builder->addNamespace('Opl', '../src/'); $builder->addNamespace('Opt', '../src/'); $builder->addNamespace('Symfony', '../src/'); $builder->addNamespace('Doctrine', '../src/'); $builder->addNamespace('Zend', '../src/'); $builder->addNamespace('Application', '../app/'); file_put_contents('../data/classMap.txt', serialize($builder->getMap()));
Every call to
addNamespace() runs the directory scanner that collects the information about the PHP files. If the scanner encounters a class that is already defined in the map, it overwrites the previous entry. Finally, we must serialize the complete map and save it to a file. This process can be automated, if we use the Symfony 2 Console interface. Then all we have to do is to install the provided command within the interface:
$cli->addCommands(array( new \Opl\Autoloader\Command\ClassMapBuild(), ));
Now we can create a builder configuration file:
[config] outputFile = "./data/classMap.txt" extension = ".php" [namespaces] Opl = "./src/" Opt = "./src/" Symfony = "./src/" Zend = "./src/" Doctrine = "./src/" Application = "./app/"
And build the map:
$ php cli.php opl:autoloader:build-class-map ./data/classMap.ini
4. Benchmark ↑
Is Open Power Autoloader really that fast? Let's take a look at a benchmark. We have an environment with 200 classes grouped into 10 libraries represented by top-level namespaces. It is a typical situation encountered in modern web applications. In addition to the framework code, we often use plenty of third party tools, and the application itself has also its own namespace. Each autoloader had to load these 200 classes and build their objects. The trial was repeated 20 times, and after throwing away the worst result, the average time was calculated. The autoloader configuration time was not a subject of the measurement.
|Autoloader name||Average time||Comments|
|Manual loading||0.0907 s|
|OPL ClassMapLoader||0.1005 s|
|OPL GenericLoader||0.1218 s|
|Doctrine 2 Class Loader||0.1450 s||A single instance can handle only one namespace - we have to register 10 autoloaders, not compatible with PSR-0|
|SplClassLoader||0.1649 s||A single instance can handle only one namespace - we have to register 10 autoloaders.|
|Symfony 2 Universal Class Loader||0.1826 s||Allows to set different paths for sub-namespaces|
|Zend Framework 1.11 Loader||0.2092 s||Advanced configuration and feature set, based on
SplClassLoader and Doctrine 2 Class Loader can actually be faster than OPL GenericLoader, but only if we have a single top-level namespace. Because every new namespace requires another autoloader object to be created, the complexity of the autoloading operation increases. It does not happen in OPL Generic Loader, where the processing time is independent from the number of namespaces we have registered.
5. Conclusion ↑
Open Power Autoloader is the best choice for modern PHP projects. The collection is constantly improved, optimized and extended with new autoloaders and tools in order to give you the greatest performance. It is also distributed as a fully standalone package, and via PEAR which simplifies the integration with third party projects. If you look for extra information about it, please visit the Programmer's manual.
This text is licensed under Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States license.
Questions and feedback
This is the end of this article. Please, leave a comment.