Maison >développement back-end >tutoriel php >Analyse comparative du chargement automatique autoload et spl_autoload
Explication détaillée du mécanisme de chargement automatique PHP
(1) Présentation du mécanisme de chargement automatique
Quand utiliser PHP Lors du développement de systèmes en mode OO, il est généralement d'usage de stocker l'implémentation de chaque classe dans un fichier séparé. Cela facilite la réutilisation des classes et est également pratique pour la maintenance future. C’est aussi l’une des idées de base de la conception OO. Avant PHP5, si vous devez utiliser une classe, il vous suffit de l'inclure directement en utilisant include/require. Voici un exemple pratique :
Le code est le suivant :
/* Person.class.php */ <?php class Person { var $name, $age; function construct ($name, $age) { $this->name = $name; $this->age = $age; } } ?> /* no_autoload.php */ <?php require_once (”Person.class.php”); $person = new Person(”Altair”, 6); var_dump ($person); ?>
Dans cet exemple, le fichier no-autoload.php doit utiliser la classe Person , qui utilise require_once, l'inclut, et vous pouvez ensuite utiliser la classe Person directement pour instancier un objet.
Mais à mesure que l'échelle du projet continue de s'étendre, l'utilisation de cette méthode entraînera des problèmes cachés : si un fichier PHP doit utiliser de nombreuses autres classes, alors de nombreuses instructions require/include sont nécessaires, donc là are Peut entraîner l'omission ou l'inclusion de fichiers de classe inutiles. Si un grand nombre de fichiers nécessitent l’utilisation d’autres classes, ce serait un cauchemar de s’assurer que chaque fichier contient le bon fichier de classe.
PHP5 fournit une solution à ce problème, qui est le mécanisme de chargement automatique des classes. Le mécanisme de chargement automatique permet aux programmes PHP d'inclure automatiquement les fichiers de classe uniquement lorsque les classes sont utilisées, au lieu d'inclure tous les fichiers de classe depuis le début. Ce mécanisme est également appelé chargement différé.
Ce qui suit est un exemple d'utilisation du mécanisme de chargement automatique pour charger la classe Person :
Le code est le suivant :
/* autoload.php */ <?php function autoload($classname) { require_once ($classname . “class.php”); } $person = new Person(”Altair”, 6); var_dump ($person); ?>
Habituellement lorsque PHP5 utilise une classe, si S'il s'avère que cette classe n'est pas chargée, la fonction autoload() sera automatiquement exécutée. Dans cette fonction, nous pouvons charger les classes que nous devons utiliser. Dans notre exemple simple, nous ajoutons directement le nom de la classe avec l'extension "title="extension">extension ".class.php" pour former le nom du fichier de classe, puis utilisons require_once pour le charger. À partir de cet exemple, nous pouvons voyez que le chargement automatique doit faire au moins trois choses. La première chose est de déterminer le nom du fichier de classe en fonction du nom de la classe, et la deuxième chose est de déterminer le chemin du disque où se trouve le fichier de classe (dans notre exemple, il s'agit de l'emplacement du fichier de classe). le cas le plus simple. Les classes sont dans le même dossier que les fichiers du programme PHP qui les appellent). La troisième chose est de charger les classes du fichier disque dans le système. La troisième étape est la plus simple, utilisez simplement include/require pour. réaliser la première. La première étape et la deuxième étape de la fonction doivent s'accorder sur la méthode de mappage entre le nom de la classe et le fichier disque pendant le développement. Ce n'est qu'ainsi que nous pourrons trouver son fichier disque correspondant en fonction du nom de la classe
.
Par conséquent, lorsqu'il y a un grand nombre de classes, lorsque le fichier doit être inclus, il suffit de déterminer les règles correspondantes, puis de faire correspondre le nom de la classe avec le fichier disque réel dans la fonction autoload() pour y parvenir. l'effet du chargement paresseux. De là, nous pouvons également voir l'effet de autoload(). La chose la plus importante dans l'implémentation de la fonction est l'implémentation des règles de mappage entre le nom de la classe et le fichier disque réel
<.>Mais maintenant le problème survient. Si vous devez utiliser de nombreuses autres bibliothèques de classes dans l'implémentation d'un système, ces bibliothèques de classes peuvent être écrites par différents développeurs et les règles de mappage entre leurs noms de classe et les fichiers disque réels sont différentes. ce cas, si vous souhaitez implémenter le
chargement automatique du fichier de bibliothèque de classes, vous devez utiliser la fonction autoload(), la fonction autoload() peut être très compliquée voire impossible à implémenter au final. La fonction autoload() peut être très lourde. Même si elle peut être implémentée, elle entravera la maintenance future et l'efficacité du système aura un impact négatif important. Dans ce cas, n'existe-t-il pas une solution plus simple et plus claire ? bien sûr : NON ! Avant d'examiner d'autres solutions, jetons un coup d'œil à PHP. Comment le mécanisme de chargement automatique est-il implémenté ? Dans le processus, la première étape consiste à compiler le fichier PHP dans une séquence de bytecode communément appelée OPCODE (en fait compilée en octet). tableau appelé zend_op_array), et la deuxième étape consiste à exécuter ces OPCODE par une machine virtuelle. Tous les comportements de PHP sont implémentés par ces OPCODE. Par conséquent, afin d'étudier le mécanisme d'implémentation de l'autoload en PHP, nous compilons le fichier autoload.php. dans l'opcode, puis étudiez ce que PHP fait dans le processus en fonction de ces OPCODE : /* autoload.php La liste OPCODE compilée est réalisée à l'aide de l'outil OPDUMP développé par l'auteur
*/
Le code est le suivant : <?php
// require_once (”Person.php”);
function autoload ($classname) {
0 NOP
0 RECV 1
if (!class_exists($classname)) {
1 SEND_VAR !0
2 DO_FCALL ‘class_exists' [extval:1]
3 BOOL_NOT $0 =>RES[~1]
4 JMPZ ~1, ->8
require_once ($classname. “.class.php”);
5 CONCAT !0, ‘.class.php' =>RES[~2]
6 INCLUDE_OR_EVAL ~2, REQUIRE_ONCE
}
7 JMP ->8
}
8 RETURN null
$p = new Person('Fred', 35);
1 FETCH_CLASS ‘Person' =>RES[:0]
2 NEW :0 =>RES[$1]
3 SEND_VAL ‘Fred'
4 SEND_VAL 35
5 DO_FCALL_BY_NAME [extval:2]
6 ASSIGN !0, $1
var_dump ($p);
7 SEND_VAR !0
8 DO_FCALL ‘var_dump' [extval:1]
?>
在 autoload.php的第10行代码中我们需要为类Person实例化一个对象。因此autoload机制一定会在该行编译后的opcode中有所体 现。从上面的第10行代码生成的OPCODE中我们知道,在实例化对象Person时,首先要执行FETCH_CLASS指令。我们就从PHP对 FETCH_CLASS指令的处理过程开始我们的探索之旅。
通过查阅PHP的源代码(我使用的是PHP 5.3alpha2版本)可以发现如下的调用序列:
ZEND_VM_HANDLER(109, ZEND_FETCH_CLASS, …) (zend_vm_def.h 1864行)
=> zend_fetch_class (zend_execute_API.c 1434行)
=>zend_lookup_class_ex (zend_execute_API.c 964行)
=> zend_call_function(&fcall_info, &fcall_cache) (zend_execute_API.c 1040行)
在最后一步的调用之前,我们先看一下调用时的关键参数:
/* 设置autoload_function变量值为”autoload” */
fcall_info.function_name = &autoload_function; // Ooops, 终于发现”autoload”了
…
fcall_cache.function_handler = EG(autoload_func); // autoload_func !
zend_call_function 是Zend Engine中最重要的函数之一,其主要功能是执行用户在PHP程序中自定义的函数或者PHP本身的库函数。zend_call_function有两个 重要的指针形参数fcall_info, fcall_cache,它们分别指向两个重要的结构,一个是zend_fcall_info, 另一个是zend_fcall_info_cache。zend_call_function主要工作流程如下:如果 fcall_cache.function_handler指针为NULL,则尝试查找函数名为fcall_info.function_name的函 数,如果存在的话,则执行之;如果fcall_cache.function_handler不为NULL,则直接执行 fcall_cache.function_handler指向的函数。
现在我们清楚了,PHP在实例化一个 对象时(实际上在实现接口,使用类常数或类中的静态变量,调用类中的静态方法时都会如此),首先会在系统中查找该类(或接口)是否存在,如果不存在的话就 尝试使用autoload机制来加载该类。而autoload机制的主要执行过程为:
(1) 检查执行器全局变量函数指针autoload_func是否为NULL。
(2) 如果autoload_func==NULL, 则查找系统中是否定义有autoload()函数,如果没有,则报告错误并退出。
(3) 如果定义了autoload()函数,则执行autoload()尝试加载类,并返回加载结果。
(4) 如果autoload_func不为NULL,则直接执行autoload_func指针指向的函数用来加载类。注意此时并不检查autoload()函数是否定义。
真 相终于大白,PHP提供了两种方法来实现自动装载机制,一种我们前面已经提到过,是使用用户定义的autoload()函数,这通常在PHP源程序中 来实现;另外一种就是设计一个函数,将autoload_func指针指向它,这通常使用C语言在PHP扩展中实现。如果既实现了 autoload()函数,又实现了autoload_func(将autoload_func指向某一PHP函数),那么只执行 autoload_func函数。
(3) SPL autoload机制的实现
SPL 是Standard PHP Library(标准PHP库)的缩写。它是PHP5引入的一个扩展库,其主要功能包括autoload机制的实现及包括各种Iterator接口或类。 SPL autoload机制的实现是通过将函数指针autoload_func指向自己实现的具有自动装载功能的函数来实现的。SPL有两个不同的函数 spl_autoload, spl_autoload_call,通过将autoload_func指向这两个不同的函数地址来实现不同的自动加载机制。
spl_autoload 是SPL实现的默认的自动加载函数,它的功能比较简单。它可以接收两个参数,第一个参数是$class_name,表示类名,第二个参 数$file_extensions是可选的,表示类文件的扩展名" title="扩展名">扩展名,可以在$file_extensions中指定多个扩展名" title="扩展名">扩展名,护展名之间用分号隔开即 可;如果不指定的话,它将使用默认的扩展名" title="扩展名">扩展名.inc或.php。spl_autoload首先将$class_name变为小写,然后在所有的 include path中搜索$class_name.inc或$class_name.php文件(如果不指定$file_extensions参数的话),如果找 到,就加载该类文件。你可以手动使用spl_autoload(”Person”, “.class.php”)来加载Person类。实际上,它跟require/include差不多,不同的它可以指定多个扩展名" title="扩展名">扩展名。
Comment faire fonctionner spl_autoload automatiquement, c'est-à-dire pointer autoload_func vers spl_autoload ? La réponse est d'utiliser la fonction spl_autoload_register. En appelant spl_autoload_register() pour la première fois dans un script PHP sans aucun paramètre, vous pouvez pointer autoload_func vers spl_autoload.
Grâce à la description ci-dessus, nous savons que la fonction de spl_autoload est relativement simple, et qu'elle est implémentée dans l'extension SPL, et nous ne pouvons pas étendre sa fonction. Et si vous souhaitez mettre en œuvre votre propre mécanisme de chargement automatique plus flexible ? A cette époque, la fonction spl_autoload_call fait ses débuts.
Jetons d'abord un coup d'œil aux merveilleuses fonctionnalités de l'implémentation de spl_autoload_call. À l'intérieur du module SPL, il y a une variable globale autoload_functions, qui est essentiellement une HashTable, mais nous pouvons simplement la considérer comme une liste chaînée. Chaque élément de la liste chaînée est un pointeur de fonction, pointant vers une fonction de chargement automatique avec . Fonction de classe fonction. L'implémentation de spl_autoload_call elle-même est très simple. Elle exécute simplement chaque fonction de la liste chaînée dans l'ordre. Après l'exécution de chaque fonction, il est jugé si la classe requise a été chargée. Si le chargement est réussi, elle retourne directement et ne le fait pas. continuer à exécuter la liste chaînée d’autres fonctions. Si la classe n'a pas été chargée après l'exécution de toutes les fonctions de cette liste chaînée, spl_autoload_call se terminera directement sans signaler d'erreur à l'utilisateur. Par conséquent, l'utilisation du mécanisme de chargement automatique ne garantit pas que la classe sera automatiquement chargée correctement. La clé dépend toujours de la manière dont votre fonction de chargement automatique est implémentée.
Alors, qui gère la liste des fonctions de chargement automatique autoload_functions ? Il s'agit de la fonction spl_autoload_register mentionnée précédemment. Il peut enregistrer la fonction de chargement automatique définie par l'utilisateur dans cette liste chaînée et pointer le pointeur de la fonction autoload_func vers la fonction spl_autoload_call (notez qu'il existe une exception et que la situation spécifique est laissée à la réflexion de chacun). Nous pouvons également supprimer les fonctions enregistrées de la liste chaînée autoload_functions via la fonction spl_autoload_unregister.
Comme mentionné dans la section précédente, lorsque le pointeur autoload_func n'est pas nul, la fonction autoload() ne sera pas automatiquement exécutée. Maintenant, autoload_func a pointé vers spl_autoload_call. Que devons-nous faire si nous voulons toujours le chargement automatique. () fonction pour travailler? Bien sûr, utilisez toujours l'appel spl_autoload_register(autoload) pour l'enregistrer dans la liste chaînée autoload_functions.
Revenons maintenant à la dernière question de la première section, nous avons une solution : implémentez leurs propres fonctions de chargement automatique en fonction des différents mécanismes de dénomination de chaque bibliothèque de classes, puis utilisez spl_autoload_register pour les enregistrer dans la fonction de chargement automatique SPL. respectivement. Mettez-le simplement dans la file d'attente. De cette façon, nous n'avons pas besoin de maintenir une fonction de chargement automatique très complexe.
(4) Problèmes d'efficacité du chargement automatique et contre-mesures
Lors de l'utilisation du mécanisme de chargement automatique, la première réaction de nombreuses personnes est que l'utilisation du chargement automatique réduira l'efficacité du système. Certaines personnes suggèrent même de ne pas utiliser le chargement automatique pour des raisons d'efficacité. Après avoir compris le principe de l'implémentation du chargement automatique, nous savons que le mécanisme de chargement automatique en lui-même n'est pas la raison pour laquelle il affecte l'efficacité du système. Il peut même améliorer l'efficacité du système car il ne chargera pas de classes inutiles dans le système.
Alors pourquoi beaucoup de gens ont-ils l'impression que l'utilisation du chargement automatique réduira l'efficacité du système ? En fait, c'est précisément la fonction de chargement automatique conçue par l'utilisateur qui affecte l'efficacité du mécanisme de chargement automatique. S'il ne peut pas faire correspondre efficacement le nom de la classe au fichier disque réel (remarque, cela fait référence au fichier disque réel, pas seulement au nom du fichier), le système devra effectuer de nombreuses vérifications de l'existence du fichier (nécessitant dans chaque chemin d'inclusion ( pour rechercher dans le chemin inclus dans le fichier), et déterminer si le fichier existe nécessite des opérations d'E/S disque. Comme nous le savons tous, l'efficacité des opérations d'E/S disque est très faible, c'est donc le coupable qui réduit l'efficacité. du mécanisme de chargement automatique !
Par conséquent, lorsque nous concevons le système, nous devons définir un mécanisme clair pour mapper les noms de classe aux fichiers de disque réels. Plus cette règle est simple et claire, plus le mécanisme de chargement automatique sera efficace.
Conclusion : Le mécanisme de chargement automatique n'est pas naturellement inefficace. Seul un abus de l'autoload et des fonctions de chargement automatique mal conçues entraîneront une réduction de son efficacité.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!