Accueil » Tous les articles » Composer » Comprenons composer – Autoloader

Comprenons composer – Autoloader

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

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

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

La 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;
PHP

On 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.

  1. 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).
  2. 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)
  3. 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.
  4. 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.
  5. Inclusion du fichier de classe :
    • La fonction de chargement automatique utilise include ou require pour inclure le fichier de la classe demandée dans le script en cours.
    • Par exemple, si la classe MaClasse est dans le fichier MaClasse.php, ce fichier est inclus.
  6. 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();.
  7. 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é.

Besoin d’aide pour mettre cela en place ?


Publié

dans

par

Commentaires

Laisser un commentaire