Aug 18 2008

[PHP] Writting Custom Class Autoloader Using SPL

Category: Articles,PHPFractalizeR @ 1:11 am

My approach to writting mid-to-huge applications is to put each class to separate file and autoload class files on demand to eliminate the use of include/require statements completely. Autoloading in some cases can even speed up your project (for example, my scripts rarely throw exceptions. Exception classes are loaded by demand and only in case exception happend, so I remove

<?php
require_once('DatabaseException.php');
require_once('UserBOException.php);

overhead from my project. However, writting a good universal autoloader class is not trivial, however, simple. In this article I would like to suggest my own lightweigh multifunctional autoloader class.

If you are not familiar with autoloading – it is just a technology to load files, containing class code, when this class is first met in PHP executable code. Please read this section of PHP manual before reading further.

The target is to write a class, that is capable of:

  • Loading classes from pre-defined directory structure (FR_DB_Connection class for example is to be loaded from api/DB/Connection.php, FR_Exception – from api/Exception.php, FR_DB_Connection_Extender from api/DB/Connection.Extender.php etc
  • Loading single classes not matching scheme from predefined files (like Smarty and Smarty_Compiler for example)
  • Using a custom class-load function for third-party libraries with another class-to-directory-structure mappings.

In my autloader I distinguish classes by prefix. For my class library it is FL_ and FR_. For Zend it is Zend_ etc.

So, in autoloader interface we make two functions to register autloading rules: registerClass and registerPrefixed. I also make this class static to eliminate a need to create any instance and to ease of calling spl_autoload_register.

<?php
/**
 * Class containing autoload routines
 *
 */
class FL_FLAutoloader {
 
	/**
	 * Class prefix-to-folder and prefix-to-callable storage
	 *
	 * @var array
	 */
	private static $_prefixMappings = array ();
 
	/**
	 * Class-to-file mappings storage
	 *
	 * @var array
	 */
	private static $_class2File = array ();
 
	/**
	 * Function registers a class prefix-to-folder mapping to load classes from
	 *
	 * @param string $prefix
	 * @param string|callable $rootFolder
	 */
	public static function registerPrefixed($prefix, $item) {
		if ((! is_dir($item)) and (! is_callable($item))) {
			throw new FL_Exception("Item passed is neither callable, nor a directory!");
		}
		self::$_prefixMappings [$prefix] = $item;
	}
 
	/**
	 * Function registers classname-to-file mapping to load classes
	 *
	 * @param string $className
	 * @param string $filename
	 */
	public static function registerClass($className, $filename) {
		if (! file_exists($filename)) {
			throw new FL_Exception('File does not exist!');
		}
		self::$_class2File [$className] = $filename;
 
	}

So, code is pretty straightforward. Some examples:

<?php
FL_FLAutoloader::registerPrefixed('MyPrefix', '/usr/lib/MyPHPLib/');
FL_FLAutoloader::registerPrefixed('MyComplexLib', array('ComplexLibAutoloader', 'autoload'));
FL_FLAutoloader::registerClass('Smarty', '/usr/lib/Smarty/Smarty.php');
FL_FLAutoloader::registerClass('Smarty_Compiler', '/usr/lib/Smarty/Smarty_Compiler.php');

Main autoloading function will look like this:

<?php
	/**
	 * Main autoloading function
	 *
	 * @param string $className
	 */
	public static function autoload($className) {
		//Checking single classes
		if (isset(self::$_class2File [$className])) {
			require_once self::$_class2File [$className];
			return;
		}
 
		//Checking prefixed
		$classNameParts = explode('_', $className);
		if (isset(self::$_prefixMappings [$classNameParts [0]])) {
			$item = self::$_prefixMappings [$classNameParts [0]];
 
			//If this prefix registered as callable - calling load handler
			if (is_callable($item)) {
				call_user_func($item, $className);
				return;
			}
 
			//Else it is a path to library root from which to load this class
			unset($classNameParts [0]);
			$pathToClassFile = implode(DIRECTORY_SEPARATOR, $classNameParts);
			$fileNameToInclude = $item . '/' . $pathToClassFile . '.php';
			if (! file_exists($fileNameToInclude)) {
				throw new FL_Exception(
					"Class file for class `$className` does not exist!" . $fileNameToInclude);
			}
 
			require_once $fileNameToInclude;
		}
		//We do not throw exception here because class can be handled by another autoloader at stack.
	//So, if we did't find any association between class name/prefix in our DB we just skip it
	//to leave loading to other autoloaders
	}

First we search if class name-to-file mapping is registered. If it does, we autoload file we need. Next we check for prefix. If such prefix is registered, we check, if it’s handler is callable. If it is, we just call it. If it is not – we consider handler to be a path to library root, then construct full file name from given details and load file.

In case given prefix is not registered, we just skip all procedure. Probably, there are other autoloaders on SPL stack to handle it.

We just need to mention a line in a file with our autoloader class to register an autoload function:

<?php
spl_autoload_register(array ('FL_FLAutoloader', 'autoload' ));

I utilize my custom Exception class in the above code, but one can replace it with regular Exception class. It doesn’t matter.

And that’s all, actually 🙂

Tags: , , ,

Leave a Reply

You must be logged in to post a comment. Login now.