PHP Autoloaders
Tags: php
Published: 2024-01-06 22:18:46

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](#require)
- [SPL](#spl)
- [Composer](#composer)
- [PSR-4](#psr4)

---

<a id="require"></a>
### 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
<?php

namespace App\Controller;

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

```

#### Person Class

```php
<?php

namespace App\Model;

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

```

#### Example Classes

```php
<?php

namespace Book\Example;

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

```

```php
<?php

namespace Book\Example;

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

```

```php
<?php

namespace Book\Example;

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

```

#### Testing

```php
<?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

```php
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
```

---

<a id="spl"></a>
### 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
<?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
<?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

```php
object(Book\Example\Example1)[2]

object(Book\Example\Example2)[3]

object(Book\Example\Example3)[4]

Class Example1
Class Example2
Class Example3
```

#### AppLoader Class

```php
<?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

```php

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

```php

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.

---


<a id="composer"></a>
### 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:

```json
{
	"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
<?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

```php
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
```

---

<a id="psr4"></a>
### PSR-4

[PSR-4 is auto-loading standard](https://www.php-fig.org/psr/psr-4/) 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:

```JSON
{
	"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
<?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

```php
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`.