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.
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.
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()
.
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 valeurlib
App
la valeursrc
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 dossierlib
).App
fait référence aux classes de l’application de l’utilisateur (situées dans le dossiersrc
).
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 :
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
ousrc
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
.
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 etdirname()
retourne le chemin du répertoire parent)implode('/', $namespaceParts)
: la reconstitution du namespace, réécrite par notre script (en remplaçant respectivementPlugo
etApp
parlib
etsrc
).php
: l’extension d’un fichierPHP
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
:
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 :
namespace Plugo\Temp;
class Demo {
// ...
}
Ensuite, l’importer avec un use
:
use Plugo\Temp;
new Demo();
Une fois cela, vous pourrez l’instancier car l’autoload va automatiquement l’inclure.