How to use PHP autoloader to require files containing namespaced classes

Typewriter with a piece of paper on which it says "Artificial Intelligence".
It's not rocket science. It's artificial intelligence.

If you're building an object-oriented PHP application that's larger than a handful of files, it can be cumbersome to require them one by one. On top of that, you have to manually keep adding your new files. And if you have classes relying on other classes, you have to keep track of the order in which you require them.

There's one function that can solve these challenges and automatically include your classes: spl_autoload_register(). Here's how to make it work.

Setting up your project

Let's assume you have a project with a file structure like this:

App.php
|- Command
   |- Command.php
   |- Debug.php
|- Console
   |- Argument.php
   |- Option.php

And each file uses a namespace to match their path (Command\Debug), plus one general namespace to encapsulate the app (App):

App.php

namespace App;

class App {}

Command.php

namespace App\Command;

abstract class Command {}

Debug.php

namespace App\Command;

class Debug extends Command {}

Argument.php

namespace App\Console;

class Argument {}

Option.php

namespace App\Console;

class Option {}

In summary, the App namespace exists to group your classes together, while Command and Console make up the path in which their respective files are found.

Unless you're building a library, your app will have an entry point, and in this case that's App.php. In other words, App.php contains the first class that gets instantiated and responsible for working with the remaining classes.

Abstracting the autoloading process

I prefer not to litter App.php with code that doesn't necessarily pertain to the execution of the app. Because of this, I create a separate file called Autoloader.php and place it at the same level as App.php.

App.php
Autoloader.php
|- Command
   |- Command.php
   |- Debug.php
|- Console
   |- Argument.php
   |- Option.php

Autoloader.php

<?php

namespace App;

/**
 * @author Ryan Sechrest
 * @package App
 */
class Autoloader
{
    /**
     * Register autoloader
     */
    public function __construct()
    {
        spl_autoload_register([$this, 'register']);
    }

    /**
     * Register autoloader to require classes on demand
     *
     *  1. Remove app name from namespace: 'App\Foo\Bar' => 'Foo\Bar'
     *  2. Convert namespace to path: 'Foo\Bar' => 'Foo/Bar'
     *  3. Append PHP file extension: 'Foo/Bar' => 'Foo/Bar.php'
     *  4. Require file if it exists
     *
     * @param string $class
     */
    public function register(string $class): void
    {
        $class = substr($class, strlen(__NAMESPACE__ . '\\'));
        $class = str_replace('\\', '/', $class);
        $class = $class . '.php';
        if (!file_exists($class)) {
            return;
        }
        require_once $class;
    }
}

The default constructor calls spl_autoload_register() and passes the Autoloader instance and register() method as a callback to spl_autoload_register(). Also note that $class is being passed to you by the callback.

Let's take a closer look at register().

Once we have the class name, we remove App from the namespace, because as mentioned earlier, that name only exists to logically group our classes. In other words, none of our classes are nested in a directory called App.

We then convert the back slashes from our namespace (e.g. Console\Argument) to forward slashes (e.g. Console/Argument), and you can see how this is looking more like a path to a file now.

Last, we append .php to the class, which gives us the path to our file (e.g. Console/Argument.php).

Initializing the autoloader

The only step left is to actually initialize our Autoloader class. A good place for that would be the default constructor in our App class.

App.php

namespace App;

class App
{
    public function __construct()
    {
    	require_once 'Autoloader.php';
    	new Autoloader();
    }
}

Now any time a class is utilized in our application, PHP will look for a class based on the rules within our Autoloader and include it via require_once as defined in register() automatically.

Hopefully this provided some clarity on how the autoloader works, and if you have any questions or comments, feel free to leave them below.

Featured image by Markus Winkler.