Overview
The current ZF autoloader strategy is quite simple, and based on PEAR standards: each class has a 1:1 relationship to the filesystem, and classes must be found on the include_path, with the following autoloader implementation covering all classes:
This implementation is straight-forward. However, it has drawbacks:
- It is surprisingly difficult to educate users about the {{include_path|| and its proper usage.
- In many ways, it would be much easier and simpler to be able to load the autoloader class by either the absolute path or a path relative to the calling script, and simply fire it off.
- The deeper the ZF library is within the include_path, the slower autoloading becomes.
- It does not address systems where 1:1 relationships do not exist.
- Even on systems using opcode caches, stat calls tend to pile up due to the need to search the include_path, making this a slow solution.
- It does not easily address architectures where components may be installed under individual trees.
I hereby propose some alternatives for ZF2.
Changes to standard autoloader
To address points 1, 1.a, 2, 4, and 5, some relatively simple changes may be made.
First, the autoloader can be altered to contain all functionality it needs to function; this will make it possible to simply require it by absolute path or a path relative to the calling script, and immediately start autoloading.
Second, the autoloader should have the ability to accept pairs of namespaces and paths. As an example, if /opt/lib/ZendFramework-2.0.0/library/Zend contains the ZF library, you would do one of the following operations:
These simple changes have a huge impact on ease-of-use, configurability, and performance.
Finally, the approach will still allow wildcard usage (i.e., specifying no prefix and/or no path). This will allow it to act as a fallback autoloader if desired.
This approach offers performance gains of 20% over the ZF1 autoloader with no opcode cache, and ~40% gains with opcode caching, but only when used with explicit namespace/path pairs.
- See the Symfony2 UniversalAutoloader implementation for a similar example
- See the SplClassLoader implementation as another example
Class Map Autoloading
A number of other projects utilize "class maps", which are simply hashes of class/file pairs. The file may either be an absolute path, or a path relative to the directory in which the map is defined.
Examples of such solutions include:
The basic approach involves creating a script that performs static analysis over a tree to find classfiles and generate the classmap. From there, two approaches are possible:
- A self-registering class map
- Files that simply return the map, to be consumed by an autoloader
The first approach offers the easiest solution from an end-users point of view:
However, it can potentially lead to a plethora of such autoloaders, which can potentially lead to slowdown by spl_autoload. Additionally, it doesn't allow for extension or merging classmap lists (which would allow for userland rewrites of classes).
The approach suggested is two-fold:
- A script that can create self-registering class maps
- Another script for creating simple classmaps (as PHP arrays) that can then be used by an autoloader class
The latter approach allows usage such as the following:
This latter approach makes it easy to configure the autoloader with multiple classmaps.
A build tool would be created to allow generating class maps at build/deployment time, and this tool would be hooked into the package creation process for the project itself, so that ZF will ship with a class map by default.
Either approach offers performance gains of 25% over the ZF1 autoloader with no opcode cache, and 150% with an opcode cache. As such, these approaches offer the best performance possible short of preloading.
Autoloader Factory
With multiple autoloading strategies available, setting up and configuring autoloading becomes potentially more difficult. We propose an autoloader factory to simplify the process, and tie in with Zend_Application. The factory would take as configuration items:
- Autoloader class to use
- Any options to pass to that autoloader
As an example:
As you can see, it would simply return an array or object containing the various autoloaders created, and implicitly register each with spl_autoload.
Theory of Operation
The overview contains a number of implementation ideas already. In this section, we will discuss more of the nuts and bolts associated with each approach.
Autoloader interface
All autoloaders WILL:
- Take configuration options via the constructor and optionally a "configuration" method (name to be determined)
- Provide an "autoload($class)" method, which will perform the logic necessary to attempt to load a class file
- Supply a "register()" method which, when invoked, will register the autoloader with spl_autoload_register()
Autoloaders WILL NOT:
- Act as singletons
Proposed Interface
Standard Autoloader
The standard autoloader WILL:
- Autoload PSR-0-compliant classes
- Allow specifying Namespace/Path pairs; when specified, it will look in the provided path, and only that path, to load any class with that namespace. Multiple pairs may be provided.
- Allow specifying Vendor Prefix/Path pairs; these will work just like Namespace/Path pairs, but will check for vendor prefixes instead.
- Allow specifying either a namespace or vendor prefix without a path; in this case, the include_path will be used.
- Allow specifying no namespace with a path; in this case, it will act as a fallback autoloader, searching for classfiles on the given path.
- Allow specifying no namespace and no path; in this case, it will act as a fallback autoloader, searching on the include_path.
The standard autoloader WILL NOT:
- Act as a fallback autoloader by default. You will need to specifically configure it as such.
Proposed Implementation
- See the PSR-0 autoloader implementation on github as an example.
Class Map Autoloader
The classmap autoloader WILL:
- Allow registering EITHER files returning class maps OR array class maps.
- Registering multiple class maps will merge them; class maps registered later will override those previously merged when conflicts occur.
- Class map definitions MUST define the file to use an absolute path; by preference, _DIR_ will be used for portability.
Proposed Implementation
- See the class mapautoloader implementation on github as an example.
Additional Tools
Additionally, with the classmap autoloader, two tools will be provided:
- A tool for generating the classmaps. By default, this will create files named ".classmap.php", using _DIR_ to prefix the paths.
- A tool for generating self-registering class maps. By default, this will create a file named "_autoload.php", using the namespace in the current directory (or provided by a switch). Within the file, it will register a closure with spl_autoload_register(), and use the class map within it to perform lookups.
These tools may be provided either as Zend_Tool providers, or as standalone scripts.
- See the class file locater implementation on github for an example of how to find class files.
- See the class map generator on github for an example of a script for generating class files.
- See the autoloader generator on github for an example of a script for generating self-registering class map autoloaders.
Autoloader Factory
The autoloader factory WILL:
- Allow specifying autoloader class/option pairs to configure and register
- Allow specifying the class file where the autoloader class may be found
- Register all configured autoloaders with spl_autoload
- Return a list of autoloader class/instance pairs
8 Comments
comments.show.hideSep 13, 2010
Marc Bennewitz (private)
The include file "_autoload.php" registers a closure directly with spl_autoload_register but after this it's very hard to unregister this clusure if you have more closures registered this way.
-> Is it possible to return the closure?
Sep 13, 2010
Matthew Weier O'Phinney
Absolutely, it'd be possible to return the closure. The question is: why?
Yes, I understand the only way to unregister a callback from the spl_autoload registry is by handle – but how often do you really do this in PHP? The only time I've done so myself is unit testing – and then only to test autoloaders. I'm thinking the YAGNI principle applies here.
Sep 14, 2010
Marc Bennewitz (GIATA mbH)
I looked for the following:
The script generating the autoloader file is designed for ZF but if I understand it right it's usable on other libraries/components. There it could be that you have a big library with hundreds of class files but you only need a small extract on a specific manner. If you can unregister the big library the memory for a big $map-array can be free.
Sep 14, 2010
Matthew Weier O'Phinney
First: yes, you can use the utility on any library. It simply does static analysis, looking for classes and interfaces under that tree in order to generate the map.
Based on your scenario, though, I'd argue that you should create autoloaders or class maps for the invididual components in the library you're pulling from, instead of using one that pulls in the entire library's map.
Sep 14, 2010
Pádraic Brady
One small comment, since I love the rest. The Autoloader factory contains:
It's early days, but will the all-empty array reference be strictly required? It's loose wiring type stuff like this that can cause moments of API confusion.
Sep 14, 2010
Matthew Weier O'Phinney
Yes, that can likely be omitted.
Sep 14, 2010
Dolf Schimmel (Freeaqingme)
The CR-Team recommends accepting this proposal as-is.
May 13, 2011
Jacob Oettinger
How about allowing the standard autoloader to cache file vs. class mappings as they are resolved?
Depending on the cache performance this would possibly make it as fast as the class map autoloader when a previously used class is loaded.
It would, like the Zend_Db_Table metadata cache, require that the cache is cleared when code is updated. It could be used without caching while developing and with cachen when in production.