Understanding Autoloaders

How and why to use an autoloader to load your classes

By Bob Ray  |  January 7, 2025  |  4 min read
Understanding Autoloaders

In previous articles, Understanding Namespaces and Using the Use Statement with Namespaces, we looked at various ways to prevent collisions between classes, constants, and functions by using namespaces. In this article, we'll see how to make that more convenient by using an autoloader that loads class files automatically without include or require statements.

Autoloaders Explained

In modern versions of PHP (since Version 5.1.2), whenever you try to instantiate a class with new and the code for that class has not been loaded, PHP checks to see if anyone has registered an autoload function. If there is a registered autoloader, PHP calls it with the class name as an argument. If there is more than one registered autoloader, PHP will call each of them in turn until the class is successfully loaded, or all the autoloaders fail to find the class.

As long as your autoloader is capable of loading the requested classes, all your classes, and their functions and constants, will load automatically when you call new. This saves you the trouble of specifying the path to the class file, and using include or require make sure PHP has the code for the class.

Why User an Autoloader

With an autoloader, you only need a single include or require statement to include the autoloader's PHP file. Once you've done that, and registered your autoloader with a single line of code, you can instantiate any class with new and the autoloader will find and include the class file. This is convenient, but it's not the only advantage of using an autoloader.

Using an autoloader means that only the classes that are actually used in the code are loaded. With include and require, you need to load all classes that might be used in the code. There may be classes in your code that are seldom used, with an autoloader, they'll only be included when necessary. This is especially useful if you have many classes stored in separate files.

A final advantage of using an autoloader is that it eliminates the possibility of including the same class file more than once. PHP keeps track of the classes that have already been loaded and will never load the same class twice. This means you never have to use if (class_exists()) or multiple include or require statements. Once you've included the autoloader and registered it (which usually happens inside the autoloader) you can use new at will, without worrying about whether the class exists, and if all your functions are inside classes, you never have to worry about function-name collisions.

The Plan

In this article, we'll create a simple autoloader to load classes for a project. The example autoloaders in this article are not meant for actual use (though they're fully functional). I've intentionally made them crude and simple to make it easier to understand how autoloaders work.

As we said in the previous articles, namespaces are often used to help prevent collisions between functions, classes, and files with the same name. In addition, Composer is available to handle the installation and upgrade of packages. Composer provides its own autoloader, which makes creating your own unnecessary. We'll look at how to use the Composer autoloader in an upcoming article.

Our Project

We'll use the VeterinaryOffice project from the previous articles. The examples in this article assume that there are no namespaces involved. We'll see how to combine namespaces with an autoloader in my next article. If you've created the project (with the Dog, Cat, and Controller classes), you'll be able to use it, but you will have to comment out the namespace line at the top of every file for this non-namespace example.

To simplify things, our first autoloader will load only two classes Cat and Dog. Our project is under a project root defined by the constant PROJECT_BASE_PATH. It should be the path to the VeterinaryOffice directory and should end with a slash (/). The class files will be under PROJECT_BASE_PATH' . 'src/Model/Pets/. There's no slash before src/ because PROJECT_BASE_PATH ends with one.

Here's the project structure:

/* PROJECT_BASE_PATH is a constant that will
   be set to the full path of the VeterinaryOffice
   directory; it must end with a slash */

PROJECT_BASE_PATH
    src/
        Model/
            Pets/
                Dog.php
                Cat.php
        Controllers
            Controller.php
        vendor
            autoload.php

Important: The PROJECT_BASE_PATH for this article should not include the src/ directory. It will be applied in the autoloader.

If you have the files from the previous articles, just comment out the namespace line at the top of all files. If not, the classes are shown just below. Then, just add the vendor directory (as shown above) and an empty file under it called autoload.php. The code for the autoload.php file will be provided below.

Comment out any namespace lines in all files. I've left them commented out in the files below because we'll use them in later articles. If you are creating the files from scratch, this is all they need to have in them:

Our Dog.php file contains this code:

<?php
// namespace VeterinaryOffice\Model\Pets;

class Dog {

    function __construct() {
      /* constructor code here */
    }

    function speak() {
        return 'arf';
    }
}

Our Cat.php file looks like this:

<?php
// namespace VeterinaryOffice\Model\Pets;
class Cat {
    function __construct() {
    }

    function speak() {
        return 'meow';
    }
}

Here's the Controller class file:

<?php
namespace VeterinaryOffice\Controllers;

class Controller {
    function __construct() {
        /* constructor code here */
    }

    public static function sayHello() {
        echo "\nController says Hello!";
    }
    /* Other controller methods here */
}

The Autoloader

We've put the autoload.php file in the vendor directory because this is a widely accepted location for autoloaders. In a later article, when we use Composer's autoload system, it will create the autoloader in that location.

The autoload.php file (which goes in the PROJECT_BASE_PATH . vendor/autoload/ directory) looks like this:

<?php
function my_autoloader($class) {
    $path = PROJECT_BASE_PATH . 'src/Model/Pets/' . $class . '.php';
    // echo "PATH: " . $path;
    if (file_exists($path)){
        include $path;
        return true;
    }
    return false;
}

/* Resister the autoloader with PHP */
spl_autoload_register('my_autoloader');

Our crude autoloader simply "includes" the class file based on its name. Now we can freely instantiate our classes (and any others in the Pets/ directory) just by calling new with the actual class name. The commented-out echo line is useful for making sure the path is correct.

An autoloader must return true if the file can be loaded and false if not. This is because ours might not be the only registered autoloader. If ours doesn't find the file, it needs to return false so any other autoloaders will run. If it does find the file, it needs to return true so they won't.

Using Our Autoloader

This simple file sets up our autoloader and calls the Cat and Dog speak() methods:

<?php
const PROJECT_BASE_PATH = 'full/path/to/veterinaryoffice/';
include PROJECT_BASE_PATH . 'vendor/autoload.php';

$dog = new Dog();
echo "\nDogs say: " . $dog->speak();
$cat = new Cat();
echo "\nCats say: " . $cat->speak();

Notice that we didn't have to include either of the Cat or Dog class files. The autoloader does that for us. We can also add new pet class files in the Pets/ directory (e.g., Horse.php, Sheep.php) and the autoloader will automatically load them as well when we need to use them. An additional advantage is that only the classes actually used in a particular file will be loaded.

Because we set the PROJECT_BASE_PATH constant outside of any function in our code above, it has global scope, so it will be available in the autoloader file.

How does the Autoloader Know what Class to Load?

Once you've registered the autoloader, any time PHP runs into a class name it doesn't recognize, it automatically calls the autoloader with the name of the class as the only argument ($class in our autoloader).

Our autoloader simply adds that class name to the end of the path, adds the .php suffix, and includes it.

Limitations

This is a pretty bargain-basement autoloader. It has no error handling, and it assumes that all the class files are in the Model/pets/ directory. If we want to use our Controller class file, we either have to put it in the Model/Pets/ directory, or change our code.

One solution is to name our classes after their locations, using an underscore for the directory separator. In that case, we'd have Model_Pets_Dog class, a Model_Pets_Cat class, and a Controllers_Controller class. Our class file names would remain unchanged. In our autoloader, we'd use code like this:

<?php
function my_autoloader($class) {
    $basePath = PROJECT_BASE_PATH . 'src/';
    $path = str_replace('_', DIRECTORY_SEPARATOR, $class);
    $path = PROJECT_BASE_PATH . $path . '.php';
    // echo "\nPATH: " . $path;

    if (file_exists($path)){
        include $path;
        return true;
    }

    return false;
}

Now, to use our classes, we'd have to do it like this:

<?php
const PROJECT_BASE_PATH = 'full/path/to/veterinaryoffice/';
include_once PROJECT_BASE_PATH . 'vendor/autoload.php';

$dog = new Model_Pets_Dog();
$cat = new Model_Pets_Cat();
$controller = new Controllers_Controller();

If you're thinking that this is an ugly solution, I'd have to agree, though it is common in older autoloaders. It's difficult to remember the path to each class, which is now part of the class name. In addition, you have to make sure that no class has an extra underscore in its name.

Another Way

A somewhat cleaner solution, is to include the possible paths to class files in the autoloader and search all of them. By doing this, we can go back to our original class name. Here's an example with added error handling:

<?php
function my_autoloader($class) {
    $dirs = array(
        'Model/Pets/',
        'Controllers/',
        'Processors/',
    );

    $file = '';
    foreach($dirs as $dir) {
        $file = PROJECT_BASE_PATH . 'src/' . $dir . $class;

        if (file_exists($file)) {
            include $file;
            return true;
        }
    }
    return false;
}

spl_autoload_register('my_autoloader');

Now, we can do this to use our classes:

<?php
const PROJECT_BASE_PATH = 'full/path/to/veterinaryoffice/';
include_once PROJECT_BASE_PATH . 'vendor/autoload.php';

if (class_exists('Dog')) {
    $dog = new Dog();
    echo "\nDogs say:" . $dog->speak();
} else {
    /* Handle Error */
}

if (class_exists('Controller')) {
    $controller = new Controller();
    $controller::sayHello();
} else {
    /* Handle Error */
}

Yet Another Way

For a more efficient autoloader, we can actually specify the path to each class file in an array in the autoloader, like this:

<?php
function my_autoloader($class) {
    $petDir = PROJECT_BASE_PATH . 'src/Model/Pets/';
    $controllerDir = PROJECT_BASE_PATH . 'src/Controllers/';

    $classmap = array(
        'Cat' => $petDir . 'Cat.php',
        'Dog' => $petDir . 'Dog.php',
        'Controller' => $controllerDir . 'Controller.php',
        );

    if (array_key_exists($classmap, $class)) {
        if (file_exists($classmap[$class])) {
            include $classmap[$class];
            return true;
        }
    }
    return false;
}
spl_autoload_register('my_autoloader');

This method is faster at run-time, but not so convenient for us, since we have to add a new line to the autoloader for every class file we create. If you are using the Composer autoloader (more on this in an upcoming article), though, it will create the classmap array for you automatically and inject it into the autoloader file. It will even do this for your own class files in addition to the files Composer has installed.

Wrapping Up

Though we used an array of directories in our autoloader to solve the problem of class files located in different places, a more elegant solution is to use namespaces. That's beyond the scope of this article. We'll see how to use namespaces with an autoloader in my next article.


Bob Ray is the author of the MODX: The Official Guide and dozens of MODX Extras including QuickEmail, NewsPublisher, SiteCheck, GoRevo, Personalize, EZfaq, MyComponent and many more. His website is Bob’s Guides. It not only includes a plethora of MODX tutorials but there are some really great bread recipes there, as well.