PHP Autoloaders

Antonio Silva - Jan 6 - - Dev Community

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
Enter fullscreen mode Exit fullscreen mode

PersonController Class

<?php

namespace App\Controller;

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

Enter fullscreen mode Exit fullscreen mode

Person Class

<?php

namespace App\Model;

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

Enter fullscreen mode Exit fullscreen mode

Example Classes

<?php

namespace Book\Example;

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

Enter fullscreen mode Exit fullscreen mode
<?php

namespace Book\Example;

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

Enter fullscreen mode Exit fullscreen mode
<?php

namespace Book\Example;

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

Enter fullscreen mode Exit fullscreen mode

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();

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
    }
}

Enter fullscreen mode Exit fullscreen mode
  • 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();

Enter fullscreen mode Exit fullscreen mode

Output

object(Book\Example\Example1)[2]

object(Book\Example\Example2)[3]

object(Book\Example\Example3)[4]

Class Example1
Class Example2
Class Example3
Enter fullscreen mode Exit fullscreen mode

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;
    }
}

Enter fullscreen mode Exit fullscreen mode
  • 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();

Enter fullscreen mode Exit fullscreen mode

Output


object(Person)[6]

object(PersonController)[7]

Class Person
Class PersonController
Enter fullscreen mode Exit fullscreen mode
  • 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"]
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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"
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • Use PSR-4 for organizing directory and class files.
  • Use the composer dump-autoload command to generate the autoload.php file.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .