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 :
Zend\Loader\AutoloaderFactory::
factory();
Sans argument, la fabrique instancie un objet de type Zend\Loader\StandardAutoloader par défaut :
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;
}
[&
#8230;]
}
La méthode « getStandardAutoloader() » crée une instance de cet objet :
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 :
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 :
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 :
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 :
Et le dossier « Prefix/ » un autre fichier Test.php :
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 :
$autoloader
=
Zend\Loader\AutoloaderFactory::
getRegisteredAutoloader(Zend\Loader\AutoloaderFactory::
STANDARD_AUTOLOADER);
$autoloader
->
registerPrefix('
Prefix_
'
,
'
./autoload-dir/Prefix
'
);
new Prefix_Test();
$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() » :
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 :
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é :
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;
}
[&
#8230;]
}
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() » :
$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 :
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 :
<?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 :
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 :
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
);
}
}
}
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 :
Zend\Loader\AutoloaderFactory::
factory();
Zend\Loader\AutoloaderFactory::
factory(array('
Zend\Loader\ClassMapAutoloader
'
=>
array('
config/application.autoload_classmap.php
'
)));
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 :
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 :
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 :
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 :
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 :
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.