I. Prérequis

La version du Zend Framework 2 utilisée pour cet article est la 2.0.0beta3. L'utilisation du Zend Framework 2 nécessite au minimum la version 5.3.3 de PHP.

Zend Framework 2.0 RequirementsZend Framework 2.0 Requirements

II. Les autoloaders du Zend Framework 2

Dans la liste des axes d'amélioration du Zend Framework 2 figurent les autoloaders. Trop souvent basés sur l'include_path, les autoloaders du Zend Framework 1 sont trop gourmands en ressources. Si l'utilisation des préfixes de classe permet de rendre le chargement de classes un peu plus performant, cette étape reste tout de même coûteuse en performances. De nouveaux autoloaders ont fait leur apparition pour rendre le processus encore plus rapide. Faisons un tour d'horizon des différents autoloaders du Zend Framework 2.

III. L'autoloader standard

L'autoloader Standard, de type Zend\Loader\StandardAutoloader, permet de charger une classe suivant un préfixe de nom ou un namespace. Il est aussi possible de configurer l'autoloader standard afin d'utiliser l'include_path, ce qui serait préjudiciable en termes de performances.

Voyons un exemple de l'utilisation de cet autoloader. Notre fichier index.php l'utilise par défaut avec l'instruction :

index.php
Sélectionnez
Zend\Loader\AutoloaderFactory::factory();

Sans argument, la fabrique instancie un objet de type Zend\Loader\StandardAutoloader par défaut :

Zend\Loader\AutoloaderFactory.php
Sélectionnez
const STANDARD_AUTOLOADER = 'Zend\Loader\StandardAutoloader';

public static function factory($options = null){
    if (null === $options) {
        if (!isset(static::$loaders[static::STANDARD_AUTOLOADER])) {
            $autoloader = static::getStandardAutoloader();
            $autoloader->register();
            static::$loaders[static::STANDARD_AUTOLOADER] = $autoloader;
        }
        return;
    }
    […]
}

La méthode « getStandardAutoloader() » crée une instance de cet objet :

Zend\Loader\AutoloaderFactory.php
Sélectionnez
protected static function getStandardAutoloader(){
    if (null !== static::$standardAutoloader) {
        return static::$standardAutoloader;
    }
    $stdAutoloader = substr(strrchr(static::STANDARD_AUTOLOADER, '\\'), 1);
    if (!class_exists(static::STANDARD_AUTOLOADER)) {
        require_once __DIR__ . "/$stdAutoloader.php";
    }
    $loader = new StandardAutoloader();    static::$standardAutoloader = $loader;
    return static::$standardAutoloader;
}

La méthode « register() » enregistre la fonction « autoload() » de l'objet courant, comme méthode de chargement, auprès de la SPL :

Zend\Loader\StandardAutoloader.php
Sélectionnez
public function register(){
    spl_autoload_register(array($this, 'autoload'));
}

Le constructeur de l'autoloader standard enregistre par défaut le namespace « Zend » afin qu'il soit tout de suite prêt à l'emploi :

Zend\Loader\StandardAutoloader.php
Sélectionnez
public function __construct($options = null){
    $this->registerNamespace('Zend', dirname(__DIR__));
    if (null !== $options) {
        $this->setOptions($options);
    }
}

Ceci explique la possibilité d'instancier ensuite un objet du namespace « Zend » sans difficulté.
Les méthodes permettant l'enregistrement de préfixes et namespaces sont :

Zend\Loader\StandardAutoloader.php
Sélectionnez
public function registerNamespace($namespace, $directory){
    $namespace = rtrim($namespace, self::NS_SEPARATOR). self::NS_SEPARATOR;
    $this->namespaces[$namespace] = $this->normalizeDirectory($directory);
    return $this;
}

public function registerPrefix($prefix, $directory){
    $prefix = rtrim($prefix, self::PREFIX_SEPARATOR). self::PREFIX_SEPARATOR;
    $this->prefixes[$prefix] = $this->normalizeDirectory($directory);
    return $this;
}

Ajoutons un dossier à la racine, nommé « ./autoload-dir », qui contient deux dossiers « Namesp/ » et « Prefix/ ». Le dossier « Namesp » possède un fichier Test.php :

Test.php
Sélectionnez
namespace Namesp;

class Test{}

Et le dossier « Prefix/ » un autre fichier Test.php :

Test.php
Sélectionnez
class  Prefix_Test{}

Afin de charger ces deux classes, il nous suffit de récupérer l'autoloader créé et d'y ajouter les namespaces ou préfixes dont on a besoin :

utilisation des préfixes
Sélectionnez
$autoloader = Zend\Loader\AutoloaderFactory::getRegisteredAutoloader(Zend\Loader\AutoloaderFactory::STANDARD_AUTOLOADER);
$autoloader->registerPrefix('Prefix_','./autoload-dir/Prefix');

new Prefix_Test();
utilisation des namespaces
Sélectionnez
$autoloader = Zend\Loader\AutoloaderFactory::getRegisteredAutoloader(Zend\Loader\AutoloaderFactory::STANDARD_AUTOLOADER);
$autoloader->registerNamespace('Namesp','./autoload-dir/Namesp');

new Namesp\Test();

L'utilisation du StandardAutoloader est très simple. Comme on l'a dit précédemment, il est aussi possible d'utiliser la directive « include_path » depuis la méthode « setFallbackAutoloader() » :

utilisation de la directive "include_path"
Sélectionnez
set_include_path(implode(PATH_SEPARATOR, array(    realpath('./autoload-dir'), get_include_path())));
$autoloader = Zend\Loader\AutoloaderFactory::getRegisteredAutoloader(Zend\Loader\AutoloaderFactory::STANDARD_AUTOLOADER);
$autoloader->setFallbackAutoloader(true);

new Namesp\Test();

Si nous activons l'autoload avec la directive « include_path » en enregistrant tout de même des namespaces ou préfixes, le framework donnera la priorité aux fichiers avec préfixe ou namespace :

Zend\Loader\StandardAutoloader.php
Sélectionnez
const NS_SEPARATOR     = '\\';

public function autoload($class){
    $isFallback = $this->isFallbackAutoloader();
    if (false !== strpos($class, self::NS_SEPARATOR)) {
        if ($this->loadClass($class, self::LOAD_NS)) {
            return $class;
        } elseif ($isFallback) {
            return $this->loadClass($class, self::ACT_AS_FALLBACK);
        }
        return false;
    }
    if (false !== strpos($class, self::PREFIX_SEPARATOR)) {
        if ($this->loadClass($class, self::LOAD_PREFIX)) {
            return $class;
        } elseif ($isFallback) {
            return $this->loadClass($class, self::ACT_AS_FALLBACK);
        }
        return false;
    }
    if ($isFallback) {
        return $this->loadClass($class, self::ACT_AS_FALLBACK);
    }
    return false;
}

L'autoloader vérifie alors si le chargement concerne un nom de classe avec namespace (simple vérification de la présence d'un double-slash) et utilise la méthode «loadClass() » afin de la charger. C'est seulement si le chargement n'a pas abouti que l'autoloader appelle cette même méthode, qui utilisera alors la directive « include_path », comme nous l'avons spécifié :

Zend\Loader\StandardAutoloader.php
Sélectionnez
protected function loadClass($class, $type){
    if (!in_array($type, array(self::LOAD_NS, self::LOAD_PREFIX, self::ACT_AS_FALLBACK))) {
        require_once __DIR__ . '/Exception/InvalidArgumentException.php';
        throw new Exception\InvalidArgumentException();
    }

    if ($type === self::ACT_AS_FALLBACK) {
        $filename     = $this->transformClassNameToFilename($class, '');
        $resolvedName = stream_resolve_include_path($filename);
        if ($resolvedName !== false) {
            return include $resolvedName;
        }
        return false;
    }
    […]
}

Comme son nom l'indique, la fonction de chargement « stream_resolve_include_path() » est basée sur la directive « include_path ». Même si l'utilisation de celle-ci est à éviter, nous pouvons nous en servir une fois nos préfixes et autres namespaces enregistrés, ceux-ci étant prioritaires lors du chargement.

Notons qu'il est aussi possible de fournir un tableau d'options directement dans le constructeur de notre objet de chargement de classe ou depuis la méthode « setOptions() » :

 
Sélectionnez
$autoloader = Zend\Loader\AutoloaderFactory::getRegisteredAutoloader(Zend\Loader\AutoloaderFactory::STANDARD_AUTOLOADER);
$autoloader->setOptions(array(
        'namespaces'=>array('Namesp'=>'./autoload-dir/Namesp'),
        'prefixes'=>array('Prefix_'=>'./autoload-dir/Prefix'),
    )
);

new Namesp\Test();
new Prefix_Test();

Afin de gagner en performances, le framework propose dans cette version, un nouvel autoloader plus rapide et encore plus simple d'utilisation, le ClassMapAutoloader.

Dans la version 1 du framework, le Zend_Loader_Autoloader permet de charger des espaces de noms (préfixes en réalité), la classe StandardAutoloader reprend un peu ce principe. Le Zend_Loader_Autoloader peut aussi enregistrer d'autres autoloaders pour un même préfixe et ceux-ci seront utilisés en priorité. Il faut aussi noter qu'il n'y a pas de notion de namespace php dans le Zend Framework 1, l'autoloader se base donc sur ce que fournit le développeur. Dans cette nouvelle version, namespace php et préfixes sont bien différenciés, ce qui rend les performances meilleures car le framework peut maintenant différencier les deux.

IV. Le ClassMapAutoloader

Associant nom de classe et chemin, le Zend\Loader\ClassMapAutoloader est certainement l'autoloader le plus performant. Dès lors que le tableau de chemins correspondant aux noms de classes est configuré, le ClassMapAutoloader est fonctionnel.

Prenons un exemple avec la même structure de dossier que pour l'autoloader standard de la section précédente :

 
Sélectionnez
chdir(dirname(__DIR__));
require_once (getenv('ZF2_PATH') ?: 'vendor/ZendFramework/library') . '/Zend/Loader/AutoloaderFactory.php';
Zend\Loader\AutoloaderFactory::factory();
$maps = include 'config/application.autoload_classmap.php';
Zend\Loader\AutoloaderFactory::factory(array('Zend\Loader\ClassMapAutoloader' => array($maps)));

new Namesp\Test();
new Prefix_Test();

Notre fichier de configuration est un simple tableau de type clé/valeur :

config/application.autoload_classmap.php
Sélectionnez
<?php
    return array(
        'Namesp\Test' => __DIR__ . '/../autoload-dir/Namesp/Test.php',
        'Prefix_Test' => __DIR__ . '/../autoload-dir/Prefix/Test.php',
);

Comme nous l'avons précédemment, l'instruction suivante crée un autoloader standard, puis enregistre le namespace « Zend » avant de s'inscrire auprès de la SPL :

 
Sélectionnez
Zend\Loader\AutoloaderFactory::factory();

L'autoloader par défaut permet alors le chargement de toutes les classes avec l'espace de nom « Zend », comme la classe Zend\Loader\ClassMapAutoloader que nous allons utiliser.

Ensuite, nous devons indiquer à la méthode « factory() » de la fabrique d'autoloader que nous souhaitons enregistrer une instance du ClassMapAutoloader auprès de la SPL. La méthode s'attend à recevoir en paramètre un nom de classe d'autoloader avec sa configuration associée :

Zend\Loader\AutoloaderFactory.php
Sélectionnez
public static function factory($options = null){
    if (null === $options) {
        [&#8230;]
    }
    if (!is_array($options) && !($options instanceof \Traversable)) {
        [&#8230;]
    }
    foreach ($options as $class => $options) {
        if (!isset(static::$loaders[$class])) {
            $autoloader = static::getStandardAutoloader();
            if (!class_exists($class) && !$autoloader->autoload($class)) {
                [&#8230;]
            }
            [&#8230;]
            if ($class === static::STANDARD_AUTOLOADER) {
                $autoloader->setOptions($options);
            } else {
                $autoloader = new $class($options);
            }
            $autoloader->register();
            static::$loaders[$class] = $autoloader;
        } else {
            static::$loaders[$class]->setOptions($options);
        }
    }
}
Zend\Loader\ClassMapAutoloader.php
Sélectionnez
public function setOptions($options){
    $this->registerAutoloadMaps($options);
    return $this;
}

public function registerAutoloadMaps($locations){
    [&#8230;]
    foreach ($locations as $location) {
        $this->registerAutoloadMap($location);
    }
    return $this;
}

public function registerAutoloadMap($map){
    if (is_string($map)) {
        $location = $map;
        If ($this === ($map = $this->loadMapFromFile($location))) {
            return $this;
        }
    }
    [&#8230;]
    $this->map = array_merge($this->map, $map);
    if (isset($location)) {
        $this->mapsLoaded[] = $location;
    }
    return $this;
}

La mise à jour des options entre la fusion des anciennes configurations avec les nouvelles avant de stocker le résultat. Attention, pour deux clés identiques, la fusion utilise en priorité la clé du nouveau tableau d'options, car la dernière valeur rencontrée écrase l'ancienne (pour plus de précision, je vous conseille d'aller voir directement la documentation php de « array_merge() »).

Dans notre exemple, nous avons passé en paramètre de la fabrique un tableau d'options, mais il est également possible d'indiquer au constructeur du ClassMapAutoloader un chemin de fichier de configuration. L'autoloader s'occupe alors de récupérer automatiquement les options en utilisant l'instruction de langage « include », par exemple :

index.php
Sélectionnez
Zend\Loader\AutoloaderFactory::factory();
Zend\Loader\AutoloaderFactory::factory(array('Zend\Loader\ClassMapAutoloader' => array('config/application.autoload_classmap.php')));
Zend\Loader\ClassMapAutoloader.php
Sélectionnez
public function registerAutoloadMap($map){
    if (is_string($map)) {
        $location = $map;
        If ($this === ($map = $this->loadMapFromFile($location))) {
            return $this;
        }
    }
    [&#8230;]
}

protected function loadMapFromFile($location){
    if (!file_exists($location)) {
        require_once __DIR__ . '/Exception/InvalidArgumentException.php';
        throw new Exception\InvalidArgumentException('Map file provided does not exist');
    }
    if (!$path = static::realPharPath($location)) {
        $path = realpath($location);
    }
    if (in_array($path, $this->mapsLoaded)) {
        return $this;
    }
    $map = include $path;
    return $map;
}

Avec quelques traitements en plus, les deux manières de faire sont identiques. Notons que l'objet de type ClassMapAutoloader enregistre tous les chemins de fichiers qu'on lui a donné dans la variable $mapsLoaded, ce qui permet de s'assurer de ne pas faire le traitement deux fois. En lui passant un tableau directement comme dans le premier exemple, c'est au développeur de s'assurer de ne pas faire deux fois le traitement car aucun contrôle ne sera effectué du côté de l'autoloader.

Nous savons paramétrer les autoloaders, analysons maintenant l'enregistrement auprès de la SPL ainsi que la fonction appelée lors du chargement :

Zend\Loader\ClassMapAutoloader.php
Sélectionnez
public function register(){
    spl_autoload_register(array($this, 'autoload'), true, true);
}

Le troisième paramètre passé à la méthode de la SPL indique que l'on souhaite placer notre autoloader en haut de la pile des autoloaders de notre application, par défaut les enregistrements se placent à la fin. Étant donné les très bonnes performances de notre ClassMapAutoloader, il est dans l'intérêt de l'application de l'utiliser en premier.

Comme il est indiqué lors de l'enregistrement auprès de la SPL, c'est la méthode « autoload() » qui s'occupe du chargement de la classe :

Zend\Loader\ClassMapAutoloader.php
Sélectionnez
public function autoload($class){
    if (isset($this->map[$class])) {
        require_once $this->map[$class];
    }
}

On comprend alors tout de suite l'intérêt d'utiliser le ClassMapLoader, le traitement est simple et performant. Il suffit que la classe demandée soit bien configurée depuis les options de l'autoloader afin de pouvoir l'inclure directement dans notre code sans plus de recherche.

Maintenant que nous avons saisi l'intérêt du ClassMapAutoloader, nous pourrions vouloir l'utiliser pour la bibliothèque du Zend Framework plutôt que de passer par l'enregistrement d'un namepace auprès de notre autoloader standard. Les développeurs du framework mettent à disposition un fichier « classemap_generator.php » qui permet la génération automatique d'une configuration à partir d'un répertoire cible. Il suffit de consulter les options du script afin d'en comprendre le fonctionnement :

console
Sélectionnez
php classmap_generator.php --help

Une fois le fichier de configuration généré, toutes les classes du namespace « Zend » seront automatiquement chargées depuis le ClassMapAutoloader, ce qui permet un gain de performances.

V. Le ModuleAutoloader

Comme son nom l'indique et comme nous le verrons dans le chapitre consacré aux modules, le Zend\Loader\ModuleAutoloader s'adapte uniquement à nos modules, ce que nous prouvent les premières lignes du chargement de classe :

Zend\Loader\ModuleAutoloader.php
Sélectionnez
public function autoload($class){
    // Limit scope of this autoloader
    if (substr($class, -7) !== '\Module') {
        return false;
    }
    [&#8230;]
}

L'autoloader ne travaille que si nous recherchons une classe nommée « Module », point d'entrée des modules de l'application. L'utiliser comme autoloader pour le reste de notre application n'aurait alors aucune utilité.

La classe ModuleAutoloader sera étudiée en détail dans la section dédiée aux modules.

VI. L'interface SplAutoloader

Les autoloaders ClassMapAutoloader et StandardAutoloader devraient suffire pour nos applications, leur fonctionnement étant assez complet. Cependant, il est possible de créer son propre autoloader en implémentant l'interface SplAutoloader qui définit quatre méthodes :

Zend\Loader\SplAutoloader.php
Sélectionnez
interface SplAutoloader{
    public function __construct($options = null);
    public function setOptions($options);
    public function autoload($class);
    public function register();
}

Le constructeur doit pouvoir accepter les options de l'autoloader en paramètre afin de permettre à la méthode « factory() », de la fabrique AutoloaderFactory, de pouvoir créer directement l'instance en lui passant les options en paramètre comme nous l'avons vu précédemment. La méthode « setOptions() » sera utilisée pour la mise à jour de la configuration de l'autoloader par la fabrique lorsque l'instance de la classe demandée existe déjà. Les deux autres méthodes, « register() » et « autoload() », sont également utilisées par la méthode de fabrication « factory() » de l'AutoloaderFactory.

VII. Conclusion et ressources

Désormais plus riche en fonctionnalité et plus performant, les autoloaders sont un point important du Zend Framework 2.
Cet article est tiré en partie du livre "Au cœur de Zend Framework 2"livre "Au cœur de Zend Framework 2" disponible dans le début du quatrième trimestre de l'année 2012. Dans ce livre, vous retrouverez en détails tous les composants intervenants lors du rendu de la vue.

Retrouvez mes modules ou autres fonctionnalités, créés pour le Zend Framework 2, sur mon compte github disponible à l'adresse https://github.com/blanchonvincenthttps://github.com/blanchonvincent.

Retrouvez aussi la liste des tutoriels du Zend Framework 2 sur mon compte developpez.com : blanchon-vincent.developpez.comblanchon-vincent.developpez.com.