Une fois que vous avez requis toutes les dépendances indispensables au projet, vous avez du remarquer qu’un fichier autoload.php est créé. Depuis quelques versions, il ne contient pas grand chose de plus qu’une vérification de la version de PHP et l’inclusion du fichier autoload_real.php. C’est dans ce fichier que va être définie la logique pour l’autoloader.
Suivant les projets, les fichiers peuvent être différents et c’est pourquoi nous n’aborderons dans ce billet que les aspects généraux du chargement automatique dans PHP.
Autoload_real
Ce fichier va contenir la logique générale du chargement de classes de votre projet. Dans la majorité des cas, il va contenir une classe avec un nom généré aléatoirement de la forme ComposerAutoloaderInitcebacbf7129cf109fc91b6299bacd137. La première partie est commune à tous les projets et la seconde est générée de manière aléatoire lors de l’exécution de composer install. Si la logique derrière cette génération vous intéresse, vous pouvez jeter un œil sur le fichier AutoloaderGenerator du dépôt de composer (spoiler, il s’agit d’un md5 sur un uniqid).
Une fois que vous avez trouvé le fichier et observé sa structure, vous noterez qu’il contient deux fonctions. L’une d’elle va nous intéresser plus spécialement à ce moment de l’explication. Il s’agit de getLoader. Celle présentée est issu d’un projet un peu vieux pour montrer la logique avec PHP5.6 mais dans les projets plus récents, tout sera chargé de manière statique. Cela améliore grandement les performances. Votre fonction est donc sans doute beaucoup plus courte.
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInitcebacbf7129cf109fc91b6299bacd137', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInitcebacbf7129cf109fc91b6299bacd137', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitcebacbf7129cf109fc91b6299bacd137::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInitcebacbf7129cf109fc91b6299bacd137::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequirecebacbf7129cf109fc91b6299bacd137($fileIdentifier, $file);
}
return $loader;
}
Que va faire cette fonction ?
D’abord, elle va regarder que le chargeur automatique (autoloader) n’est pas déjà initialisé. Si c’est le cas, elle va retourner ce qu’elle a. On regarde si la version de PHP correspond aux prérequis de projet grâce à plateform_check.php. Ces fichiers sont générés automatiquement par composer et vous n’avez pas à les modifier. Si vous le faites, il seront réinitialisés à la prochaine installation du projet. Je dis ça parce que j’ai déjà vu quelqu’un changer la version requise de PHP… Si votre environnement ne correspond pas aux prérequis d’une dépendance, essayé de mettre à jour votre environnement ou de prendre une version plus ancienne de la dépendance.
Une fois ces vérifications faites, on va enregistrer le chargeur auprès de l’autoloader de PHP (ligne 12). Ensuite, on va l’instancier et l’affecter à la propriété loader pour une utilisation ultérieure (ligne 13). Enfin, on va l’enlever des autoloader de PHP pour retourner à la logique de base (ligne 14). La partie la plus importante est la ligne 13 (un peu comme dans le métro parisien mais en moins chaotique) qui va instancier la classe ClassLoader qui permettra par la suite de charger les différentes classes. Cette classe peut varier d’un projet à l’autre mais l’idée générale est d’initialiser le dossier vendor.
De la ligne 16 à la ligne 36, les instructions vont servir à initialiser les classes et dépendances du projet. Cela va se faire de manière statique à partir de PHP 5.6 si vous n’utilisez pas HHVM (le moteur de Facebook plus ou moins tombé en désuétude) ou zend_loader_file_encoded (si vous avez peur qu’on vous vole votre code source mais que vous ne l’avez pas mis à jour depuis une demie-décénie). Dans les cas où vous avez du vieux (voire très vieux) code ou HHVM, l’initialisation des classes et des dépendances du projet va se faire de manière dynamique. On va d’abord charger les espaces de noms, puis les dossiers correspondants selon le standard PSR4 et enfin on va charger les correspondances de classes.
Dans les deux cas, l’idée sera de fournir un mapping de classes au chargeur automatique (autoloader) afin qu’il puisse instancier correctement les classes.
On enregistre ensuite le loader auprès de PHP à l’aide de $loader->register(true). La fonction appelée est la suivante. Il servira lors de l’appel de n’importe quelle classe dynamique du projet.
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
PHPDans notre appel, on dit que notre loader ait la préséance sur les autres déjà créés par la classe. Notre loader va utiliser la fonction loadClass de la même classe comme autoloader. Celle-ci va regarder si le fichier existe à l’aide d’une fonction nommée findFile. Si celui-ci existe, on essaie d’inclure le fichier.
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
PHPLa première chose est de regarder dans le mapping fourni grâce à l’initialisation faite dans getLoader. D’autres logiques sont ensuite suivies pour essayer de trouver un fichier si le mapping n’est pas déclaré comme définitif et que la classe ne manque pas.
Pour revenir à la fonction getLoader, elle parcourt les dossiers et les fichiers tel que définis dans chaque cas de figure et inclure les fichiers des classes statiques. Il est à noté que toutes les fichiers ne sont pas requis dès le chargement. Seuls ceux clairement identifiés comme statiques le seront.
Pour information, si vous travaillez sur un projet récent, vous aurez quelque chose qui ressemblera à cela :
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit4f16566a5cb530bea89d694abc385e23', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit4f16566a5cb530bea89d694abc385e23', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit4f16566a5cb530bea89d694abc385e23::getInitializer($loader));
$loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInit4f16566a5cb530bea89d694abc385e23::$files;
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}, null, null);
foreach ($filesToLoad as $fileIdentifier => $file) {
$requireFile($fileIdentifier, $file);
}
return $loader;
PHPOn voit que l’autoloader est créé à partir d’une fonction anonyme dans le fichier ClassLoader. Il en est de même pour requérir les fichiers des classes statiques.
Que se passe-t-il lorsque j’instancie une classe ?
On va diviser le cheminement en 7 étapes principales qui contiennent chacune leur logique propre.
- Instanciation d’une classe :
- On tente d’instancier une classe, par exemple :
$objet = new MaClasse();
. - Si la classe
MaClasse
n’a pas encore été chargée, PHP déclenche le mécanisme de chargement automatique (autoloading).
- On tente d’instancier une classe, par exemple :
- Appel de l’autoloader :
- PHP cherche à résoudre la classe
MaClasse
- Il vérifie si la classe est déjà définie (peut-être par un fichier inclus précédemment)
- PHP cherche à résoudre la classe
- Autoloader sollicité :
- Si la classe n’est pas encore chargée, PHP fait appel à la fonction de chargement automatique.
- Cette fonction a été définie à l’aide de
spl_autoload_register
comme vu au paragraphe précédent. \Composer\Autoload\ClassLoader::loadClass dans notre cas.
- Fonction de chargement automatique personnalisée :
- La fonction de chargement automatique reçoit le nom de la classe demandée en tant que paramètre.
- Elle doit résoudre ce nom de classe en trouvant et en incluant le fichier correspondant.
- Inclusion du fichier de classe :
- La fonction de chargement automatique utilise
include
ourequire
pour inclure le fichier de la classe demandée dans le script en cours. - Par exemple, si la classe
MaClasse
est dans le fichierMaClasse.php
, ce fichier est inclus.
- La fonction de chargement automatique utilise
- Instanciation de la classe :
- Après l’inclusion réussie du fichier de classe, l’instanciation de la classe
MaClasse
peut maintenant être réalisée sans erreur. - Le code suivant dans le script principal peut s’exécuter normalement :
$objet = new MaClasse();
.
- Après l’inclusion réussie du fichier de classe, l’instanciation de la classe
- Suite de l’exécution :
- La suite du script PHP s’exécute avec la classe chargée et instanciée avec succès.
Bien sûr, si la classe ne correspond à rien. Une erreur fatale sera levée et le code restant ne sera pas exécuté.
Conclusion
Pour faire simple, Composer va générer un certain nombre de fichiers qui vont servir à charger dynamiquement les fichiers selon certains standards définis par le PSR. Chaque fichier doit contenir une et une seule classe correspondant au nom du fichier. Si ces standards sont respectés, le fichier sera requis par PHP et la classe sera instancié.
Dans un prochain billet, nous verrons la logique derrière les classes générées automatiquement. Notamment les fabriques (factory) et les mandataires (proxy).
Laisser un commentaire
Vous devez vous connecter pour publier un commentaire.