Maison >développement back-end >tutoriel php >Analyse détaillée du mécanisme de chargement automatique en php
__autoload implémente le chargement automatique ; cependant, en raison de l'introduction de bibliothèques multi-classes, la maintenance de __autoload sera compliquée, donc spl_aotoload spl implémente l'enregistrement manuel et la suppression d'une liste de fonctions de chargement automatique. contenu. .
Avant l'apparition de la méthode de fonction magique __autoload() en PHP, si vous vouliez instancier 100 objets dans un fichier programme, vous deviez alors utiliser include ou require pour inclure 100 fichiers de classe, ou vous auriez 100 classes. défini dans le même fichier de classe - je pense que ce fichier sera très volumineux. Mais avec la méthode __autoload(), vous n'aurez plus à vous en soucier à l'avenir. Cette classe chargera automatiquement le fichier spécifié avant d'instancier l'objet.
Lors du développement d'un système utilisant le mode OO de PHP, il est généralement d'usage de stocker l'implémentation de chaque classe dans un fichier séparé, ce qui est très simple. permet la réutilisation des classes et facilite leur 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 :
/* 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 pour l'inclure, puis vous pouvez directement utiliser la classe Person pour instancier-le 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à sont 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 sera 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 au 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 :
/* autoload.php */ <?php function __autoload($classname) { $classpath="./".$classname.'.class.php'; if(file_exists($classpath)) { require_once($classpath); } else { echo 'class file'.$classpath.'not found!'; } } $person = new Person(”Altair”, 6); var_dump ($person); ?>
Habituellement, lorsque PHP5 utilise une classe, s'il constate que la classe n'est pas chargée, il le fera exécutez automatiquement la fonction __autoload() , 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 ".class.php" pour former le nom du fichier de classe, puis utilisons require_once pour le charger. À partir de cet exemple, nous pouvons voir que autoload 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 nom du fichier de classe. disque où se trouve le chemin du fichier de classe (dans notre cas le cas le plus simple, les classes sont dans le même dossier que le fichier programme PHP qui les appelle), et la troisième chose est de charger les classes dans le système à partir du fichier disque. . La troisième étape est la plus simple, utilisez simplement include/require. Pour réaliser les fonctions des première et deuxième étapes, la méthode de mappage entre le nom de classe et le fichier disque doit être convenue lors du développement. Ce n'est qu'ainsi que nous pourrons trouver son fichier disque correspondant en fonction du nom de classe.
Par conséquent, lorsqu'il y a un grand nombre de fichiers de classe à inclure, il suffit de déterminer les règles correspondantes, puis dans la fonction __autoload(), de faire correspondre le nom de la classe avec le fichier disque réel pour obtenir effet de chargement paresseux. De là, nous pouvons également voir que la chose la plus importante dans l'implémentation de la fonction __autoload() est l'implémentation des règles de mappage entre le nom de la classe et le fichier disque réel.
Mais maintenant vient le problème. 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 leurs noms de classe sont différents des fichiers disque réels. Les règles de mappage sont différentes. À l'heure actuelle, si vous souhaitez implémenter le chargement automatique des fichiers de bibliothèque de classes, vous devez implémenter toutes les règles de mappage dans la fonction __autoload(). Dans ce cas, la fonction __autoload() peut être très compliquée, voire impossible à implémenter. En fin de compte, la fonction __autoload() peut devenir très lourde. Même si elle peut être implémentée, elle aura un impact négatif important sur la maintenance future et l'efficacité du système. Dans ce cas, n’existe-t-il pas une solution plus simple et plus claire ? La réponse est bien sûr : NON ! Avant d'envisager d'autres solutions, examinons d'abord comment le mécanisme de chargement automatique en PHP est implémenté.
Nous savons que l'exécution des fichiers PHP est divisée en deux processus indépendants La première étape consiste à compiler le fichier PHP dans ce que l'on appelle communément <.>OPCODE séquence de bytecodes (en fait compilée dans un tableau d'octets appelé zend_op_array), la deuxième étape consiste à exécuter ces OPCODEs 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 du chargement automatique en PHP, nous avons compilé le fichier autoload.php en opcode, puis avons utilisé ces OPCODE pour étudier ce que PHP a fait dans le processus :
/* autoload.php 编译后的OPCODE列表,是使用作者开发的OPDUMP工具 * 生成的结果,可以到网站 http://www.phpinternals.com/ 下载该软件。 */ 1: <?php 2: // require_once (”Person.php”); 3: 4: function __autoload ($classname) { 0 NOP 0 RECV 1 5: 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 6: require_once ($classname. “.class.php”); 5 CONCAT !0, ‘.class.php’ =>RES[~2] 6 INCLUDE_OR_EVAL ~2, REQUIRE_ONCE 7: } 7 JMP ->8 8: } 8 RETURN null 9: 10: $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 11: 12: var_dump ($p); 7 SEND_VAR !0 8 DO_FCALL ‘var_dump’ [extval:1] 13: ?>
在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机制的主要执行过程为:
检查执行器全局变量函数指针autoload_func是否为NULL。
如果autoload_func==NULL, 则查找系统中是否定义有__autoload()函数,如果没有,则报告错误并退出。
如果定义了__autoload()函数,则执行__autoload()尝试加载类,并返回加载结果。
如果autoload_func不为NULL,则直接执行autoload_func指针指向的函数用来加载类。注意此时并不检查__autoload()函数是否定义。
真相终于大白,PHP提供了两种方法来实现自动装载机制,一种我们前面已经提到过,是使用用户定义的__autoload()函数,这通常在PHP源程序中来实现;另外一种就是设计一个函数,将autoload_func指针指向它,这通常使用C语言在PHP扩展中实现。如果既实现了__autoload()函数,又实现了autoload_func(将autoload_func指向某一PHP函数),那么只执行autoload_func函数。
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是可选的,表示类文件的扩展名,可以在$file_extensions中指定多个扩展名,护展名之间用分号隔开即可;如果不指定的话,它将使用默认的扩展名.inc或.php。spl_autoload首先将$class_name变为小写,然后在所有的include path中搜索$class_name.inc或$class_name.php文件(如果不指定$file_extensions参数的话),如果找到,就加载该类文件。你可以手动使用spl_autoload(”Person”, “.class.php”)来加载Person类。实际上,它跟require/include差不多,不同的它可以指定多个扩展名。
怎样让spl_autoload自动起作用呢,也就是将autoload_func指向spl_autoload?答案是使用spl_autoload_register函数。在PHP脚本中第一次调用spl_autoload_register()时不使用任何参数,就可以将autoload_func指向spl_autoload。
通过上面的说明我们知道,spl_autoload的功能比较简单,而且它是在SPL扩展中实现的,我们无法扩充它的功能。如果想实现自己的更灵活的自动加载机制怎么办呢?这时,spl_autoload_call函数闪亮登场了。
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 qui a la fonction de. fonction de chargement automatique des classes. L'implémentation de spl_autoload_call elle-même est très simple. Il 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 façon 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 est non nul, la fonction __autoload() ne sera pas automatiquement exécutée. Maintenant, autoload_func a pointé vers spl_autoload_call si nous voulons toujours que la fonction __autoload() le fasse. travailler, nous devrions Que faire? 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 __autoload très compliquée.
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 le plaisir. 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, ce qui affecte l'efficacité du mécanisme de chargement automatique lui-même est précisément la fonction de chargement automatique conçue par l'utilisateur. 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é.
Recommandations associées :
Mécanisme de chargement automatique PHP (chargement paresseux)
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!