PHP Autoloaders

PHP Autoloaders

Autoloading is PHP's way to automatically find classes and their corresponding files without having to require all of them manually. Autoloading is also one of the reasons why PHP code is usually organized in classes because PHP only supports autoloading for classes, not simple functions for example.

Table of content


Require

PHP require construct loads the code from an file into a script and executes that code. To load the code from a file, you specify the file path after the require keyword. When loading the file, the require construct executes the code in the loaded file.

PHP require_once is the counterpart of the include_once except that the require_once issues an error if it fails to load the file. Also, the require_once won’t load the file again if the file has been loaded.

  • Use require construct to load the code from another file into the script.

  • Use require_once construct to load the code from another file once and won’t include the file again if the file has been loaded.

  • The require and require_once are language constructs, not functions.

Directory System

📦Require
 ┣ 📂App
 ┃ ┣ 📂Controller
 ┃ ┃ ┗ 📜PersonController.php
 ┃ ┗ 📂Model
 ┃ ┃ ┗ 📜Person.php
 ┣ 📂Lib
 ┃ ┗ 📂Book
 ┃ ┃ ┗ 📂Example
 ┃ ┃ ┃ ┣ 📜Example1.php
 ┃ ┃ ┃ ┣ 📜Example2.php
 ┃ ┃ ┃ ┗ 📜Example3.php
 ┗ 📜index.php

PersonController Class

<?php

namespace App\Controller;

class PersonController
{
    public function show(): string
    {
        return 'Class PersonController';
    }
}

Person Class

<?php

namespace App\Model;

class Person
{
    public function show(): string
    {
        return 'Class Person';
    }
}

Example Classes

<?php

namespace Book\Example;

class Example1
{
    public function show(): string
    {
        return 'Class Example1';
    }
}
<?php

namespace Book\Example;

class Example2
{
    public function show(): string
    {
        return 'Class Example2';
    }
}
<?php

namespace Book\Example;

class Example3
{
    public function show(): string
    {
        return 'Class Example3';
    }
}

Testing

<?php

require_once 'App/Controller/PersonController.php';
require_once 'App/Model/Person.php';

require_once 'Lib/Book/Example/Example1.php';
require_once 'Lib/Book/Example/Example2.php';
require_once 'Lib/Book/Example/Example3.php';

use App\Controller\PersonController;
use App\Model\Person;

use Book\Example\Example1;
use Book\Example\Example2;
use Book\Example\Example3;

$e1 = new Example1();
$e2 = new Example2();
$e3 = new Example3();

$p1 = new Person();
$p2 = new PersonController();

var_dump($e1);
var_dump($e2);
var_dump($e3);

var_dump($p1);
var_dump($p2);

print $e1->show() . '<br>';
print $e2->show() . '<br>';
print $e3->show() . '<br>';

print $p1->show() . '<br>';
print $p2->show();

Output

object(Book\Example\Example1)[1]

object(Book\Example\Example2)[2]

object(Book\Example\Example3)[3]

object(App\Model\Person)[4]

object(App\Controller\PersonController)[5]

Class Example1
Class Example2
Class Example3
Class Person
Class PersonController

SPL

PHP 5.1.2 introduced the spl_autoload_register() function that automatically loads a class file whenever you use a class that has not been loaded yet.

PHP 7.2.0 deprecated the __autoload() magic function and recommended to use the spl_autoload_register() function instead.

When you use a class that has not been loaded, PHP will automatically look for spl_autoload_register() function call.

The spl_autoload_register() function accepts a callback function and calls it when you attempt to create use a class that has not been loaded.

Directory System

📦2.SPL
 ┣ 📂App
 ┃ ┣ 📂Controller
 ┃ ┃ ┗ 📜PersonController.php
 ┃ ┗ 📂Model
 ┃ ┃ ┗ 📜Person.php
 ┣ 📂Lib
 ┃ ┗ 📂Book
 ┃ ┃ ┣ 📂Core
 ┃ ┃ ┃ ┣ 📜AppLoader.php
 ┃ ┃ ┃ ┗ 📜ClassLoader.php
 ┃ ┃ ┗ 📂Example
 ┃ ┃ ┃ ┣ 📜Example1.php
 ┃ ┃ ┃ ┣ 📜Example2.php
 ┃ ┃ ┃ ┗ 📜Example3.php
 ┗ 📜index.php

ClassLoader Class

<?php

namespace Book\Core;

class ClassLoader
{
    // Array to store namespace prefixes and their corresponding base directories.
    protected array $prefixes;

    // Method to register the ClassLoader's autoloader function.
    public function register(): void
    {
        spl_autoload_register(array($this, 'loadClass'));
    }

    // Method to add a namespace and its base directory to the ClassLoader.
    public function addNamespace(string $prefix, string $base_dir, bool $prepend = false): void
    {
        // Normalize namespace prefix and base directory.
        // The trim() function removes whitespace and other predefined characters from both sides of a string.
        $prefix = trim($prefix, '\\') . '\\';
        // rtrim() removes whitespace or other predefined characters from the right side of a string.
        $base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';

        // Initialize the namespace prefix array if not set.
        if (isset($this->prefixes[$prefix]) === false) {
            $this->prefixes[$prefix] = [];
        }

        // Retain the base directory for the namespace prefix.
        if ($prepend) {
            // The array_unshift() function inserts new elements in the beginning to an array.
            array_unshift($this->prefixes[$prefix], $base_dir);
        } else {
            // The array_push() function inserts one or more elements to the end of an array.
            array_push($this->prefixes[$prefix], $base_dir);
        }
    }

    // Private method to load a class using autoloading.
    private function loadClass(string $class): mixed
    {
        // The current namespace prefix.
        $prefix = $class;

        // Iterate through the namespace names to find a mapped file name.
        while (false !== $pos = strrpos($prefix, '\\')) {
            // Retain the trailing namespace separator in the prefix.
            $prefix = substr($class, 0, $pos + 1);

            // The rest is the relative class name.
            $relative_class = substr($class, $pos + 1);

            // Try to load a mapped file for the prefix and relative class.
            $mapped_file = $this->loadMappedFile($prefix, $relative_class);
            if ($mapped_file) {
                return $mapped_file;
            }

            // Remove the trailing namespace separator for the next iteration.
            $prefix = rtrim($prefix, '\\');
        }

        // Class file not found.
        return false;
    }

    // Private method to load a mapped file based on namespace and class name.
    private function loadMappedFile(string $prefix, string $relative_class): mixed
    {
        // Check if there are base directories for this namespace prefix.
        if (isset($this->prefixes[$prefix]) === false) {
            return false;
        }

        // Iterate through base directories for this namespace prefix.
        foreach ($this->prefixes[$prefix] as $base_dir) {
             /**
             * Replace the namespace prefix with the base directory,
             * replace namespace separators with directory separators
             * in the relative class name, append with .php
             * */
            $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';

            // If the file is successfully required, return its path.
            if ($this->requireFile($file)) {
                return $file;
            }
        }

        // File not found in any base directory.
        return false;
    }

    // Private method to require a file if it exists.
    private function requireFile(string $file): bool
    {
        // If the file exists, require it and return true.
        if (file_exists($file)) {
            require_once $file;
            return true;
        }

      php  // File does not exist.
        return false;
    }
}
  • ClassLoader is a class that implements an automatic class loader in PHP, allowing the organization of code using namespaces and facilitating the dynamic loading of classes as needed.

Testing

<?php

require_once 'Lib/Book/Core/ClassLoader.php';
$al = new Book\Core\ClassLoader();
$al->addNamespace('Book', 'Lib/Book');
$al->register();

use Book\Example\Example1;
use Book\Example\Example2;
use Book\Example\Example3;

$e1 = new Example1();
$e2 = new Example2();
$e3 = new Example3();

var_dump($e1);
var_dump($e2);
var_dump($e3);

print $e1->show() . '<br>';
print $e2->show() . '<br>';
print $e3->show();

Output

object(Book\Example\Example1)[2]

object(Book\Example\Example2)[3]

object(Book\Example\Example3)[4]

Class Example1
Class Example2
Class Example3

AppLoader Class

<?php

namespace Book\Core;

use RecursiveIteratorIterator;
use RecursiveDirectoryIterator;
use Exception;

class AppLoader
{
    // Array to store directories to search for classes.
    protected $directories;

    // Method to add a directory to the AppLoader.
    public function addDirectory($directory)
    {
        $this->directories[] = $directory;
    }

    // Method to register the AppLoader's autoloader function.
    public function register()
    {
        spl_autoload_register(array($this, 'loadClass'));
    }

    // Method to load a class using autoloading based on registered directories.
    public function loadClass($class)
    {
        // Get the directories to search for the class.
        $folders = $this->directories;

        // Iterate through the registered directories.
        foreach ($folders as $folder) {
            // Check if the class file exists directly in the current directory.
            if (file_exists("{$folder}/{$class}.php")) {
                require_once "{$folder}/{$class}.php";
                return true;
            } else {
                // If the directory itself exists, search for the class file recursively.
                if (file_exists($folder)) {
                    // Iterate through files and subdirectories in the current directory.
                    foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($folder),
                             RecursiveIteratorIterator::SELF_FIRST) as $entry) {
                        // Check if the entry is a directory.
                        if (is_dir($entry)) {
                            // Check if the class file exists in the current subdirectory.
                            if (file_exists("{$entry}/{$class}.php")) {
                                require_once "{$entry}/{$class}.php";
                                return true;
                            }
                        }
                    }
                }
            }
        }

        // Class file not found in any registered directory.
        return false;
    }
}
  • The code checks whether the class file is directly in the main directory or recursively in the subdirectories of the registered directories.

  • remove the namespace from the classes Person and PersonController.

Testing


require_once 'Lib/Book/Core/AppLoader.php';
$loader = new Book\Core\AppLoader;
$loader->addDirectory(__DIR__ . '/App');
$loader->register();

$p1 = new Person();
$p2 = new PersonController();

var_dump($p1);
var_dump($p2);

print $p1->show() . '<br>';
print $p2->show();

Output


object(Person)[6]

object(PersonController)[7]

Class Person
Class PersonController
  • Use the spl_autoload_register() function to autoload the classes, interfaces, and traits.

  • The problem with the spl_autoload_register() function is that you have to implement the autoloader functions by yourself. And your autoloaders may not like autoloaders developed by other developers.

  • Therefore, when you work with a different codebase, you need to study the autoloaders in that particular codebase to understand how they work.


Composer

Composer is a dependency manager for PHP. Composer allows you to manage dependencies in your PHP project. In this tutorial, we’ll focus on how to use the Composer for autoloading classes.

First create a new file called composer.json under the project’s root folder. In the composer.json, you add the following code:

{
    "autoload": {
        "classmap": ["Lib/Book/Example", "App/Controller", "App/Model"]
    }
}

Next, launch the Command Prompt on Windows or Terminal on macOS and Linux, and navigate to the project directory.

Then, type the following command from the project directory:

composer dump-autoload

Composer will generate a directory called vendor that contains a number of files the most important file to you for now is autoload.php file.

Directory System

📦Composer
 ┣ 📂App
 ┃ ┣ 📂Controller
 ┃ ┃ ┗ 📜PersonController.php
 ┃ ┗ 📂Model
 ┃ ┃ ┗ 📜Person.php
 ┣ 📂Lib
 ┃ ┗ 📂Book
 ┃ ┃ ┣ 📂Core
 ┃ ┃ ┃ ┣ 📜AppLoader.php
 ┃ ┃ ┃ ┗ 📜ClassLoader.php
 ┃ ┃ ┗ 📂Example
 ┃ ┃ ┃ ┣ 📜Example1.php
 ┃ ┃ ┃ ┣ 📜Example2.php
 ┃ ┃ ┃ ┗ 📜Example3.php
 ┣ 📂vendor
 ┃ ┣ 📂composer
 ┃ ┗ 📜autoload.php
 ┣ 📜composer.json
 ┗ 📜index.php

Testing

<?php

require_once 'vendor/autoload.php';

use App\Controller\PersonController;
use App\Model\Person;

use Book\Example\Example1;
use Book\Example\Example2;
use Book\Example\Example3;

$e1 = new Example1();
$e2 = new Example2();
$e3 = new Example3();

$p1 = new Person();
$p2 = new PersonController();

var_dump($e1);
var_dump($e2);
var_dump($e3);

var_dump($p1);
var_dump($p2);

print $e1->show() . '<br>';
print $e2->show() . '<br>';
print $e3->show() . '<br>';

print $p1->show() . '<br>';
print $p2->show();

Output

object(Book\Example\Example1)[4]

object(Book\Example\Example2)[2]

object(Book\Example\Example3)[5]

object(App\Model\Person)[6]

object(App\Controller\PersonController)[7]

Class Example1
Class Example2
Class Example3
Class Person
Class PersonController

PSR-4

PSR-4 is auto-loading standard that describes the specification for autoloading classes from file paths.

According to the PSR-4, a fully qualified class name has the following structure:

 \<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>

The structure starts with a namespace, followed by one or more sub namespaces, and the class name.

The composer.json file looks like the following:

{
    "autoload": {
        "psr-4": {
            "App\\":"App/",
            "Book\\":"Lib/Book"
        }
    }
}

Instead using the classmap, the composer.json file now uses psr-4. The psr-4 maps the namespace "App\\" to the "App/" folder and namespace "Book\\" to the "Lib/Book" folder .

Note that the second backslash (\) in the App\ namespace is used to escape the first backslash (\).

Run the composer dump-autoload command to generate the autoload.php file:

composer dump-autoload

Directory System

📦PSR4
 ┣ 📂App
 ┃ ┣ 📂Controller
 ┃ ┃ ┗ 📜PersonController.php
 ┃ ┗ 📂Model
 ┃ ┃ ┗ 📜Person.php
 ┣ 📂Lib
 ┃ ┗ 📂Book
 ┃ ┃ ┣ 📂Core
 ┃ ┃ ┃ ┣ 📜AppLoader.php
 ┃ ┃ ┃ ┗ 📜ClassLoader.php
 ┃ ┃ ┗ 📂Example
 ┃ ┃ ┃ ┣ 📜Example1.php
 ┃ ┃ ┃ ┣ 📜Example2.php
 ┃ ┃ ┃ ┗ 📜Example3.php
 ┣ 📂vendor
 ┃ ┣ 📂composer
 ┃ ┗ 📜autoload.php
 ┣ 📜composer.json
 ┗ 📜index.php

Testing

<?php

require_once 'vendor/autoload.php';

use App\Controller\PersonController;
use App\Model\Person;

use Book\Example\Example1;
use Book\Example\Example2;
use Book\Example\Example3;

$e1 = new Example1();
$e2 = new Example2();
$e3 = new Example3();

$p1 = new Person();
$p2 = new PersonController();

var_dump($e1);
var_dump($e2);
var_dump($e3);

var_dump($p1);
var_dump($p2);

print $e1->show() . '<br>';
print $e2->show() . '<br>';
print $e3->show() . '<br>';

print $p1->show() . '<br>';
print $p2->show();

Output

object(Book\Example\Example1)[4]

object(Book\Example\Example2)[2]

object(Book\Example\Example3)[5]

object(App\Model\Person)[6]

object(App\Controller\PersonController)[7]

Class Example1
Class Example2
Class Example3
Class Person
Class PersonController
  • Use PSR-4 for organizing directory and class files.

  • Use the composer dump-autoload command to generate the autoload.php file.

Don't forget to add a reaction if the post has helped.