Detaillierte Erläuterung des PHP-Autoload-Mechanismus
(1) Übersicht über den Autoload-Mechanismus
Wann Verwendung von PHP Bei der Entwicklung von Systemen im OO-Modus ist es normalerweise üblich, die Implementierung jeder Klasse in einer separaten Datei zu speichern. Dies erleichtert die Wiederverwendung von Klassen und ist auch für die zukünftige Wartung praktisch. 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:
Der Code lautet wie folgt:
/* 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, schließt es ein, und dann können Sie die Person-Klasse direkt verwenden, um ein Objekt zu instanziieren.
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, ist 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 von Anfang an einzubinden. Dieser Mechanismus wird auch als Lazy Loading bezeichnet.
Das Folgende ist ein Beispiel für die Verwendung des Autoload-Mechanismus zum Laden der Person-Klasse:
Der Code lautet wie folgt:
/* autoload.php */ <?php function autoload($classname) { require_once ($classname . “class.php”); } $person = new Person(”Altair”, 6); var_dump ($person); ?>
Normalerweise Wenn PHP5 eine Klasse verwendet und festgestellt wird, dass diese Klasse nicht geladen ist, wird die Funktion autoload() automatisch ausgeführt. Mit dieser Funktion können wir die Klassen laden, die wir verwenden müssen. In unserem einfachen Beispiel fügen wir den Klassennamen direkt mit der Erweiterung „title="extension">extension „.class.php“ hinzu, um den Namen der Klassendatei zu bilden, und laden ihn dann mit require_once. In diesem Beispiel können wir dies tun Beachten Sie, dass Autoload mindestens drei Dinge tun muss. Erstens muss der Klassendateiname anhand des Klassennamens ermittelt werden, und zweitens muss der Festplattenpfad ermittelt werden, in dem sich die Klassendatei befindet (in unserem Beispiel ist dies der Fall). Der einfachste Fall ist, dass sich die Klassen im selben Ordner befinden wie die PHP-Programmdateien, die sie aufrufen. Der dritte Schritt ist der einfachste. Verwenden Sie dazu einfach include/require Im ersten Schritt muss sich die Funktion des zweiten Schritts auf die Zuordnungsmethode zwischen dem Klassennamen und der Festplattendatei einigen. Nur so können wir die entsprechende Festplattendatei anhand des Klassennamens finden 🎜> Das obige ist der detaillierte Inhalt vonVergleichende Analyse des automatischen Ladens von Autoload und spl_autoload. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!
Daher müssen wir bei einer großen Anzahl von Klassen nur die entsprechenden Regeln ermitteln und dann den Klassennamen mit der tatsächlichen Festplattendatei in der Funktion autoload() abgleichen Um den Effekt des verzögerten Ladens zu erzielen, können wir auch den Effekt von autoload() sehen. Das Wichtigste bei der Implementierung der Funktion ist die Implementierung der Zuordnungsregeln zwischen dem Klassennamen und der tatsächlichen Festplattendatei >
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 die Zuordnungsregeln zwischen ihren Klassennamen und den tatsächlichen Festplattendateien sind unterschiedlich Wenn Sie in diesem Fall das
automatische Laden
der Klassenbibliotheksdatei implementieren möchten, müssen Sie die Funktion autoload() verwenden. Die Funktion autoload() kann letztendlich sehr kompliziert oder sogar unmöglich sein , die Funktion autoload() ist möglicherweise sehr aufgebläht, auch wenn sie die zukünftige Wartung behindert und die Systemeffizienz erheblich beeinträchtigt. Gibt es nicht eine einfachere und klarere Lösung? ist natürlich: NEIN! Bevor wir uns weitere Lösungen ansehen, werfen wir einen Blick auf PHP. Wie wird der Autoload-Mechanismus implementiert? Der erste Schritt besteht darin, die PHP-Datei in eine Bytecode-Sequenz zu kompilieren, die üblicherweise als OPCODE bezeichnet wird ein Byte-Array namens zend_op_array), und 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, kompilieren wir das Autoload. php-Datei in Opcode umwandeln und dann anhand dieser OPCODEs untersuchen, was PHP dabei tut: /* autoload.php Die kompilierte OPCODE-Liste wird mit dem vom Autor */
entwickelten OPDUMP-Tool erstellt
Der Code lautet wie folgt: <?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="扩展名">扩展名。
Wie kann man spl_autoload automatisch zum Laufen bringen, das heißt, autoload_func auf spl_autoload verweisen? Die Antwort ist die Verwendung der Funktion spl_autoload_register. Durch den ersten Aufruf von spl_autoload_register() in einem PHP-Skript ohne Parameter können Sie autoload_func auf spl_autoload verweisen.
Durch die obige Beschreibung wissen wir, dass die Funktion von spl_autoload relativ einfach ist und in der SPL-Erweiterung implementiert ist und wir ihre Funktion nicht erweitern können. Was wäre, wenn Sie Ihren eigenen, flexibleren automatischen Lademechanismus implementieren möchten? Zu diesem Zeitpunkt feiert die Funktion spl_autoload_call ihr Debüt.
Werfen wir zunächst einen Blick auf die wunderbaren Funktionen der spl_autoload_call-Implementierung. 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 mit auf eine Autoloading-Funktion zeigt Funktion der Klasse Funktion. 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 autoload_func-Funktion nicht automatisch ausgeführt, wenn der autoload_func-Zeiger auf spl_autoload_call zeigt () Funktion zu arbeiten? 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 komplexe Autoload-Funktion pflegen.
(4) Probleme mit der Autoload-Effizienz und Gegenmaßnahmen
Bei der Verwendung des Autoload-Mechanismus ist die erste Reaktion vieler Menschen, dass die Verwendung von Autoload die Systemeffizienz verringert. Einige Leute schlagen sogar vor, Autoload aus Effizienzgründen nicht zu verwenden. 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? Tatsächlich ist es genau die vom Benutzer entworfene Autoload-Funktion, die die Effizienz des Autoload-Mechanismus beeinflusst. 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.