Efficient and convenient autoloading in PHP

PHP5 introduced the concept of auto loading to PHP. Although this new ability saves developers the nightmare of including every class file they need explicitly at the top of each file, the default implementation is somewhat limited. You can get the best out of this functionality by writing your own autoload method.

The inbuilt autoload function provided by PHP is restrictive. It searches through the defined include_path and if the appropriate file is not found in that directory it throws an error. When you are applying an OOP approach to the development of a system you often end up with a lot of class files. Having all these files in one directory is not a particularly attractive prospect for anyone who likes to keep their code organised.

We can deal with this limitation by providing PHP with a custom autoload function that is capable of scanning particular directories in a recursive manor. This will allow us to organise our code as we see fit. Below is the typical structure I would use for the class files of a website or app I was building:

Typical Application Class Structure

As you can see the autoload function we need to build must be capable of scanning recursively through all directories within the “inc” directory. Of course, searching through all these directories every time a request is made for a class file would be extremely inefficient. The solution for this is to have the autoloader also maintain a path cache for the class files. This way, when a class is found it can save the full path to the cache and the next time it is requested the search will not be necessary.

Below is the full code for the autoload function we can use to achieve this goal:

function application_autoloader($class) {
	$class = strtolower($class);
	$class_filename = 'class.'.strtolower($class).'.php';
	$class_root = dirname(__FILE__);
	$cache_file = "{$class_root}/cache/classpaths.cache";
	$path_cache = (file_exists($cache_file)) ? unserialize(file_get_contents($cache_file)) : array();
	if (!is_array($path_cache)) { $path_cache = array(); }

	if (array_key_exists($class, $path_cache)) {
		/* Load class using path from cache file (if the file still exists) */
		if (file_exists($path_cache[$class])) { require_once $path_cache[$class]; }

	} else {
		/* Determine the location of the file within the $class_root and, if found, load and cache it */
		$directories = new RecursiveDirectoryIterator($class_root);
		foreach(new RecursiveIteratorIterator($directories) as $file) {
			if (strtolower($file->getFilename()) == $class_filename) {
				$full_path = $file->getRealPath();
				$path_cache[$class] = $full_path;
				require_once $full_path;
				break;
			}
		}	

	}

	$serialized_paths = serialize($path_cache);
	if ($serialized_paths != $path_cache) { file_put_contents($cache_file, $serialized_paths); }
}

spl_autoload_register('application_autoloader');

Before we break this code down to understand more fully what it is doing we should note that this function is located in the “app.intiailise.php” file in the directory structure we looked at earlier. The reason for this is simple. It means we don’t need to explicitly specify the path we wish to use as the class root, we can simply look at the directory the file is in using dirname(__FILE__). Let’s look at the code in a bit more detail.

	$class = strtolower($class);
	$class_filename = 'class.'.strtolower($class).'.php';
	$class_root = dirname(__FILE__);

The function begins by ensuring that the name of the class being called is in lowercase (to ensure no issues with case sensitive operating systems). We then go on to establish that the file we are looking for is called “class.classname.php”. The filename format is dictated by personal preference, it could just as easily be “classname.php” or “classname.class.php” if you change it. The key point to make here is that it must be consistent so the autoloader knows what to look for. Finally we determine the root directory for all the class files which happens to be the location in which this file resides.

	$cache_file = "{$class_root}/cache/classpaths.cache";
	$path_cache = (file_exists($cache_file)) ? unserialize(file_get_contents($cache_file)) : array();
	if (!is_array($path_cache)) { $path_cache = array(); }

Next we focus on initialising the class path cache. The file is held in the cache directory (which is in the same directory as “app.intiialise.php”. The cache itself is held as a serialized array. This means it can easily be saved as a string in a file and converted back into an array again. The code simply fetches the contents of the cache file and unserializes it. There is some work to ensure that the $path_cache variable always holds an array but this should never be the case, it’s just a healthy dose of paranoia.

	if (array_key_exists($class, $path_cache)) {
		/* Load class using path from cache file (if the file still exists) */
		if (file_exists($path_cache[$class])) { require_once $path_cache[$class]; }

	} else {
		/* Determine the location of the file within the $class_root and, if found, load and cache it */
		$directories = new RecursiveDirectoryIterator($class_root);
		foreach(new RecursiveIteratorIterator($directories) as $file) {
			if (strtolower($file->getFilename()) == $class_filename) {
				$full_path = $file->getRealPath();
				$path_cache[$class] = $full_path;
				require_once $full_path;
				break;
			}
		}	

	}

	$serialized_paths = serialize($path_cache);
	if ($serialized_paths != $path_cache) { file_put_contents($cache_file, $serialized_paths); }

This is the meat of the function. We check the $path_cache to check if the file that is being requested has previously been located. If it has we can simply use the path stored in the array to require the file.

If no entry is found in the cache the function goes on to scan the child directories for the requested file. We achieve this by making use of PHP’s inbuilt RecursiveDirectoryIterator and RecursiveIteratorIterator classes.

The combinatation of these classes yields a collection of SPLFileInfo objects which contain details of all the files below the root. Once a match is found for the file that is requested we simply save it to the cache array and require the file. Finally we serialize the path cache once more and save it to the cache file only if changes have been made (this saves a little bit of unnecessary work).

Now that we have created our function we need to tell PHP to use it to autoload the files. We do this like so:

spl_autoload_register('application_autoloader');

The reason we do this instead of just overwriting PHP’s autoload function using __autoload is because spl_autoload_register allows us to stack autoloaders. This means that it will execute each of the autoloaders added to the stack in turn, newest first. If the first fails, it will move on to the second and so on until the stack is exhausted at which point PHP throws an error.

Using this method of autoloading requires that you keep the following things in mind when creating a new class file:

  • You must maintain a one class per file policy.
  • All your class files must be named with the same structure e.g. “class.classname.php”.

Some last thoughts; you could make this function slightly more efficient still by storing the unserialized cache array in a global store so that it does not need to access the file with each run. There are undoubtedly other optimisations we could make here but I think this is a pretty good starting point.

I have created a simple demo of this concept which you can download and run on your own webserver to try it out yourself. You can download it here. One important thing to note before running the demo; make the “cache” folder writeable by your webserver otherwise it will be unable to create the class path cache. Let me know your thoughts!

5 Responses to Efficient and convenient autoloading in PHP

  1. Nick says:

    Very nice post!

    This is another good page on autoloading in PHP:

    Autoloading in PHP

  2. Felix says:

    Thanks for the nice solution, but I have fixed a bug. If class B inherits from class A, then class A will not be saved in the cache, because the Autoloader function is called twice (loading first class B and then class A) and only the class B path is saved in cache. I fixed that by moving the following part before require_once $full_path;
    $serialized_paths = serialize($path_cache);
    if ($serialized_paths != $path_cache) { file_put_contents($cache_file, $serialized_paths); }

  3. Jake Jeon says:

    I had to modify some to fix a little bit in case that there’s filename changed.

    ORIGINAL:
    if (array_key_exists($class, $path_cache)) {
    /* Load class using path from cache file (if the file still exists) */
    if (file_exists($path_cache[$class])) { require_once $path_cache[$class];
    }

    EDIT:
    if (array_key_exists($class, $path_cache) && file_exists($path_cache[$class])) {
    /* Load class using path from cache file (if the file still exists) */
    require_once $path_cache[$class]
    } else {

  4. Jake Jeon says:

    Hi.
    It’s again me.

    is there a way to prevent cache file to grow as there’s no necessary files included?

  5. Ricardo Miller says:

    Nice script i’m using your script in my project its working but its only caching one file in my class folder. All the other class is setup the same as the file that is being cache. But I cant’ figure out why its only caching just the one file

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>