Heim >Backend-Entwicklung >PHP-Tutorial >Detaillierte Analyse des Autoload-Mechanismus in PHP
__autoload implementiert das automatische Laden; aufgrund der Einführung von Mehrklassenbibliotheken wird die Wartung von __autoload jedoch kompliziert, sodass spl_aotoload die manuelle Registrierung und Entfernung einer Liste automatischer Ladefunktionen implementiert Inhalt. .
Bevor die Methode der magischen Funktion __autoload() in PHP erschien, mussten Sie, wenn Sie 100 Objekte in einer Programmdatei instanziieren wollten, include oder require verwenden, um 100 Klassendateien einzubinden, sonst wären es 100 Klassen in derselben Klassendatei definiert - ich glaube, diese Datei wird sehr groß sein. Aber mit der Methode __autoload() müssen Sie sich darüber in Zukunft keine Sorgen mehr machen. Diese Klasse lädt die angegebene Datei automatisch, bevor Sie das Objekt instanziieren.
Bei der Entwicklung eines Systems im OO-Modus von PHP ist es normalerweise üblich, die Implementierung jeder Klasse in einer separaten Datei zu speichern, was sehr einfach ist ermöglicht die Wiederverwendung von Klassen und erleichtert die zukünftige Wartung. Dies ist auch eine der Grundideen des OO-Designs. Wenn Sie vor PHP5 eine Klasse verwenden müssen, müssen Sie diese nur direkt mit include/require einbinden. Hier ist ein praktisches Beispiel:
/* 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); ?>
In diesem Beispiel muss die Datei no-autoload.php die Person-Klasse verwenden, die require_once verwendet, um sie einzubinden, und dann kann die Person-Klasse direkt zum Instanziieren einer verwendet werden Objekt.
Aber da der Umfang des Projekts immer weiter zunimmt, wird die Verwendung dieser Methode einige versteckte Probleme mit sich bringen: Wenn eine PHP-Datei viele andere Klassen verwenden muss, sind viele Require/Include-Anweisungen erforderlich Dies kann dazu führen, dass unnötige Klassendateien weggelassen oder eingefügt werden. Wenn eine große Anzahl von Dateien die Verwendung anderer Klassen erfordert, wäre es ein Albtraum, sicherzustellen, dass jede Datei die richtige Klassendatei enthält.
PHP5 bietet eine Lösung für dieses Problem, nämlich den Autoload-Mechanismus von Klassen. Der Autoload-Mechanismus ermöglicht es PHP-Programmen, Klassendateien nur dann automatisch einzubinden, wenn Klassen verwendet werden, anstatt alle Klassendateien zu Beginn einzubinden. Dieser Mechanismus wird auch Lazy Loading genannt.
Das Folgende ist ein Beispiel für die Verwendung des Autoload-Mechanismus zum Laden der Person-Klasse:
/* 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); ?>
Wenn PHP5 normalerweise eine Klasse verwendet und feststellt, dass die Klasse nicht geladen ist, wird sie automatisch ausgeführt die Funktion __autoload() In dieser Funktion können wir die Klassen laden, die wir verwenden müssen. In unserem einfachen Beispiel fügen wir direkt den Klassennamen mit der Erweiterung „.class.php“ hinzu, um den Namen der Klassendatei zu bilden, und laden ihn dann mit require_once. Aus diesem Beispiel können wir ersehen, dass autoload mindestens drei Dinge tun muss: Das erste ist, den Namen der Klassendatei anhand des Klassennamens zu bestimmen, und das zweite ist, den zu bestimmen Die Festplatte, auf der sich die Klassendatei befindet (in unserem einfachsten Fall befinden sich die Klassen im selben Ordner wie die PHP-Programmdatei, die sie aufruft), und die dritte Sache besteht darin, die Klassen aus der Festplattendatei in das System zu laden . Der dritte Schritt ist der einfachste, verwenden Sie einfach include/require. Um die Funktionen des ersten und zweiten Schritts zu realisieren, muss während der Entwicklung die Zuordnungsmethode zwischen dem Klassennamen und der Festplattendatei vereinbart werden. Nur so können wir die entsprechende Festplattendatei anhand des Klassennamens finden.
Daher müssen wir bei einer großen Anzahl einzubindender Klassendateien nur die entsprechenden Regeln ermitteln und dann in der Funktion __autoload() den Klassennamen mit der tatsächlichen Festplattendatei abgleichen, um dies zu erreichen Lazy-Loading-Effekt. Hier können wir auch erkennen, dass das Wichtigste bei der Implementierung der Funktion __autoload() die Implementierung der Zuordnungsregeln zwischen dem Klassennamen und der tatsächlichen Festplattendatei ist.
Aber jetzt kommt das Problem. Wenn Sie bei der Implementierung eines Systems viele andere Klassenbibliotheken verwenden müssen, können diese Klassenbibliotheken von verschiedenen Entwicklern geschrieben werden und ihre Klassennamen unterscheiden sich von den tatsächlichen Festplattendateien. Die Zuordnungsregeln sind unterschiedlich. Wenn Sie zu diesem Zeitpunkt das automatische Laden von Klassenbibliotheksdateien implementieren möchten, müssen Sie alle Zuordnungsregeln in der Funktion __autoload() implementieren. In diesem Fall ist die Implementierung der Funktion __autoload() möglicherweise sehr kompliziert oder sogar unmöglich. Letztendlich kann die Funktion __autoload() sehr aufgebläht werden. Auch wenn sie implementiert werden kann, wird sie große negative Auswirkungen auf die zukünftige Wartung und Systemeffizienz haben. Gibt es in diesem Fall nicht eine einfachere und klarere Lösung? Die Antwort lautet natürlich: NEIN! Bevor wir uns weitere Lösungen ansehen, schauen wir uns zunächst an, wie der Autoload-Mechanismus in PHP implementiert ist.
Wir wissen, dass die Ausführung von PHP-Dateien in zwei unabhängige Prozesse unterteilt ist. Der erste Schritt besteht darin, die PHP-Datei in das zu kompilieren, was allgemein als OPCODE Bytecode-Sequenz (eigentlich in ein Byte-Array namens zend_op_array kompiliert), der zweite Schritt besteht darin, diese OPCODEs durch eine virtuelle Maschine auszuführen. Alle Verhaltensweisen von PHP werden durch diese OPCODEs implementiert. Um den Implementierungsmechanismus von Autoload in PHP zu untersuchen, haben wir daher die Datei autoload.php in Opcode kompiliert und dann diese OPCODEs verwendet, um zu untersuchen, was PHP dabei tat:
/* 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函数闪亮登场了。
Werfen wir zunächst einen Blick auf die wunderbaren Funktionen der Implementierung von spl_autoload_call. Im SPL-Modul gibt es eine globale Variable autoload_functions, die im Wesentlichen eine HashTable ist, aber wir können sie uns einfach als verknüpfte Liste vorstellen. Jedes Element in der verknüpften Liste ist ein Funktionszeiger, der auf eine Funktion zeigt, die die Funktion hat Funktion zum automatischen Laden von Klassen. Die Implementierung von spl_autoload_call selbst ist sehr einfach. Es führt einfach jede Funktion in der verknüpften Liste aus. Nachdem jede Funktion ausgeführt wurde, wird beurteilt, ob die erforderliche Klasse geladen wurde Führen Sie die verknüpfte Liste weiterhin aus. Wenn die Klasse nicht geladen wurde, nachdem alle Funktionen in dieser verknüpften Liste ausgeführt wurden, wird spl_autoload_call direkt beendet, ohne dem Benutzer einen Fehler zu melden. Daher garantiert die Verwendung des Autoload-Mechanismus nicht, dass die Klasse automatisch korrekt geladen wird. Der Schlüssel hängt immer noch davon ab, wie Ihre Autoload-Funktion implementiert ist.
Wer verwaltet also die automatische Ladefunktionsliste autoload_functions? Es handelt sich um die zuvor erwähnte Funktion spl_autoload_register. Es kann die benutzerdefinierte Autoloading-Funktion in dieser verknüpften Liste registrieren und den Funktionszeiger autoload_func auf die Funktion spl_autoload_call verweisen (beachten Sie, dass es eine Ausnahme gibt und die spezifische Situation jedem überlassen bleibt, darüber nachzudenken). Wir können registrierte Funktionen auch über die Funktion spl_autoload_unregister aus der verknüpften Liste autoload_functions löschen.
Wie im vorherigen Abschnitt erwähnt, wird die Funktion __autoload() nicht automatisch ausgeführt, wenn die Funktion __autoload() weiterhin ausgeführt werden soll Arbeit, wir sollten Was tun? Verwenden Sie natürlich weiterhin den Aufruf spl_autoload_register(__autoload), um es in der verknüpften Liste autoload_functions zu registrieren.
Nun zurück zur letzten Frage im ersten Abschnitt: Wir haben eine Lösung: Implementieren Sie ihre eigenen Autoloading-Funktionen entsprechend den unterschiedlichen Benennungsmechanismen jeder Klassenbibliothek und registrieren Sie sie dann mit spl_autoload_register bei der SPL-Autoloading-Funktion bzw. einfach in die Warteschlange stellen. Auf diese Weise müssen wir keine sehr komplizierte __autoload-Funktion pflegen.
Bei der Verwendung des Autoload-Mechanismus ist die erste Reaktion vieler Menschen, dass die Verwendung von Autoload die Systemeffizienz verringert Effizienz. Nachdem wir das Prinzip der Autoload-Implementierung verstanden haben, wissen wir, dass der Autoload-Mechanismus selbst nicht der Grund für die Beeinträchtigung der Systemeffizienz ist. Er kann sogar die Systemeffizienz verbessern, da er keine unnötigen Klassen in das System lädt.
Warum haben viele Menschen den Eindruck, dass die Verwendung von Autoload die Systemeffizienz verringert? Was tatsächlich die Effizienz des Autoload-Mechanismus selbst beeinflusst, ist genau die vom Benutzer entworfene Autoload-Funktion. Wenn der Klassenname nicht effizient mit der tatsächlichen Festplattendatei abgeglichen werden kann (beachten Sie, dass sich dies auf die tatsächliche Festplattendatei bezieht, nicht nur auf den Dateinamen), muss das System umfangreiche Überprüfungen der Dateiexistenz durchführen (was in jedem Include-Pfad erforderlich ist ( Um den in der Datei enthaltenen Pfad zu durchsuchen und festzustellen, ob die Datei vorhanden ist, sind Festplatten-E/A-Vorgänge erforderlich. Wie wir alle wissen, ist die Effizienz von Festplatten-E/A-Vorgängen sehr gering, daher ist dies der Übeltäter, der die Effizienz verringert des Autoload-Mechanismus!
Daher müssen wir beim Entwurf des Systems einen klaren Mechanismus für die Zuordnung von Klassennamen zu tatsächlichen Festplattendateien definieren. Je einfacher und klarer diese Regel ist, desto effizienter ist der Autoload-Mechanismus.
Fazit: Der Autoload-Mechanismus ist nicht von Natur aus ineffizient. Nur der Missbrauch von Autoload und schlecht konzipierte Autoload-Funktionen führen zu einer Verringerung seiner Effizienz.
Verwandte Empfehlungen:
Beispielanalyse für den PHP-Autoload-Betriebsmechanismus, Autoload-Beispielanalyse
PHP-Autoload-Mechanismus (Lazy Loading)
Das obige ist der detaillierte Inhalt vonDetaillierte Analyse des Autoload-Mechanismus in PHP. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!