Créer framework PHP : Autoload

Essentiel dans un projet PHP conséquent, l'autoload va importer automatiquement dans notre projet toutes les classes utilisées.

Icône de calendrier
Avancé
10 chapitres

Rôle de l’autoload

Qui dit framework orienté objet dit classes, et qui dit classes dit import. Mais quoi de plus pénible que d’importer une classe PHP manuellement avec un require à chaque fois que l’on veut l’utiliser depuis un autre fichier ?!

Heureusement, il existe un procédé nous facilitant cette lourde tâche : l’autoload.

Le principe de l’autoload est simple, nous allons pouvoir dire à notre application :

Dès qu’une classe est utilisée, occupe-toi de l’importer pour moi.

Nous allons apporter notre propre logique framework à cette mécanique d’import afin que celle-ci fonctionne avec les namespaces définis dans notre application.

Création de l’autoload

1. Fonction d’autoload

C’est avec la fonction PHP spl_autoload_register() qu’il sera possible d’implémenter cette mécanique d’inclusion automatique de classes.

autoload.php
copié !
spl_autoload_register(function (string $class): void {

});

La fonction spl_autoload_register() sera exécutée à chaque utilisation d’une classe.

Cette fonction va exécuter une fonction de callback anonyme qui récupérera dans la variable $class le nom de la classe en question. Si cette classe se situe dans un certain namespace, alors $class récupérera aussi ce namespace (ce qui sera systématiquement notre cas).

2. Éclatement du namespace

On cherche maintenant à éclater la variable $class sur le caractère \ de sorte à en isoler les différentes parties de son namespace. Cela est possible avec la fonction PHP explode().

autoload.php
copié !
spl_autoload_register(function (string $class): void {
	$namespaceParts = explode('\\', $class);
});

Si j’instancie la classe Foo\Bar\Demo, alors $namespaceParts contiendra ['Foo', 'Bar', 'Demo'].

3. Alias pour les namespaces

Ajoutons désormais dans notre code un tableau constant ALIASES dans lequel on associe aux clés :

  • Plugo la valeur lib
  • App la valeur src
autoload.php
copié !
const ALIASES = [
	'Plugo' => 'lib',
	'App' => 'src'
];

spl_autoload_register(function (string $class): void {
	$namespaceParts = explode('\\', $class);
});

L’objectif de ce tableau est de travailler en conservant de bonnes conventions de nommage au niveau de nos dossiers (📂 lib et 📂 src étant des noms de dossier assez génériques) tout en bénéficiant de namespaces plus esthétiques et « contextualisés ».

  • Plugo fait référence aux classes du framework (situées dans le dossier lib).
  • App fait référence aux classes de l’application de l’utilisateur (situées dans le dossier src).

Logiquement les classes utilisées dans notre framework devront donc se situer dans un namespace commençant par App ou Plugo.

Adaptons le code de notre autoload en fonction :

autoload.php
copié !
const ALIASES = [
	'Plugo' => 'lib',
	'App' => 'src'
];

spl_autoload_register(function (string $class): void {

	$namespaceParts = explode('\\', $class);

	if (in_array($namespaceParts[0], array_keys(ALIASES))) {
		$namespaceParts[0] = ALIASES[$namespaceParts[0]];
	} else {
		throw new Exception('Namespace « ' . $namespaceParts[0] . ' » invalide. Un namespace doit commencer par : « Plugo » ou « App »');
	}

});

Ici, on vérifie si la première portion du namespace de la classe correspond à une clé du tableau ALIASES :

  • Si c’est le cas, on redéfinit la première portion du namespace par le dossier équivalent (lib ou src en fonction).
  • Sinon, on génère une erreur PHP.

4. Inclusion dynamique de la classe

Pour inclure automatiquement une classe il nous faut connaître son emplacement. Et pour cela, nous allons procéder en respectant une règle simple : namespace = chemin physique.

Par exemple, si une classe se situe dans le namespace App\Entity, on s’attend à ce qu’elle se situe dans les dossiers 📁 src/Entity.

autoload.php
copié !
const ALIASES = [
	'Plugo' => 'lib',
	'App' => 'src'
];

spl_autoload_register(function (string $class): void {

	$namespaceParts = explode('\\', $class);

	if (in_array($namespaceParts[0], array_keys(ALIASES))) {
		$namespaceParts[0] = ALIASES[$namespaceParts[0]];
	} else {
		throw new Exception('Namespace « ' . $namespaceParts[0] . ' » invalide. Un namespace doit commencer par : « Plugo » ou « App »');
	}

	$filepath = dirname(__DIR__) . '/' . implode('/', $namespaceParts) . '.php';
	if (!file_exists($filepath)) {
		throw new Exception("Fichier « " . $filepath . " » introuvable pour la classe « " . $class . " ». Vérifier le chemin, le nom de la classe ou le namespace");
	}
	require $filepath;

});

$filepath va contenir une chaîne de caractères résultant de la concaténation de :

  • dirname(__DIR__) . '/' : est une expression qui est utilisée pour obtenir le chemin absolu vers le répertoire parent du répertoire courant (__DIR__ est une constante qui représente le chemin absolu du répertoire courant et dirname() retourne le chemin du répertoire parent)
  • implode('/', $namespaceParts) : la reconstitution du namespace, réécrite par notre script (en remplaçant respectivement Plugo et App par lib et src)
  • .php : l’extension d’un fichier PHP

Ainsi, la variable $filepath va contenir le chemin vers la classe en question.

  • Si aucun fichier n’existe, on lève une erreur
  • Sinon, on inclut la classe

Mise à jour de index.php

Notre autoload est fonctionnel ! Il ne nous reste plus qu’à l’inclure dans notre contrôleur frontal 📄 index.php :

index.php
copié !
require dirname(__DIR__) . '/lib/autoload.php';
Tester l'autoloader

Pour vérifier et comprendre le fonctionnement de l’autoload, il peut être intéressant de créer une classe temporaire dans votre projet :

lib/Temp/Demo.php
copié !
namespace Plugo\Temp;

class Demo {
	// ...
}

Ensuite, l’importer avec un use :

index.php
copié !
use Plugo\Temp;
new Demo();

Une fois cela, vous pourrez l’instancier car l’autoload va automatiquement l’inclure.