Detailed explanation of PHP autoload mechanism
(1) Overview of autoload mechanism
When using PHP When developing systems in OO mode, it is usually customary to store the implementation of each class in a separate file. This makes it easy to reuse classes and is also convenient for future maintenance. This is also one of the basic ideas of OO design. Before PHP5, if you need to use a class, you only need to include it directly using include/require. The following is a practical example:
The code is as follows:
/* 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 this example, the no-autoload.php file needs to use the Person class, which uses require_once It contains, and then you can directly use the Person class to instantiate an object.
But as the scale of the project continues to expand, using this method will bring some hidden problems: if a PHP file needs to use many other classes, then a lot of require/include statements are needed, so there are May cause omission or inclusion of unnecessary class files. If a large number of files require the use of other classes, it will be a nightmare to ensure that each file contains the correct class file.
PHP5 provides a solution to this problem, which is the automatic loading (autoload) mechanism of classes. The autoload mechanism makes it possible for PHP programs to automatically include class files only when classes are used, instead of including all class files at the beginning. This mechanism is also called lazy loading.
The following is an example of using the autoload mechanism to load the Person class:
The code is as follows:
/* autoload.php */ <?php function autoload($classname) { require_once ($classname . “class.php”); } $person = new Person(”Altair”, 6); var_dump ($person); ?>
Usually when PHP5 uses a class, if this is found If the class is not loaded, the autoload() function will be automatically run. In this function, we can load the classes we need to use. In our simple example, we directly add the class name with the extension "title="extension">extension ".class.php" to form the class file name, and then use require_once to load it. From this example , we can see that autoload has to do at least three things. The first thing is to determine the class file name based on the class name, and the second thing is to determine the disk path where the class file is located (in our example, it is the simplest case. The classes are in the same folder as the PHP program files that call them). The third thing is to load the classes from the disk file into the system. The third step is the simplest, just use include/require to achieve the first. The first step and the second step function must agree on the mapping method between the class name and the disk file during development. Only in this way can we find its corresponding disk file based on the class name.
Therefore, when there are a large number of classes. When the file is to be included, we only need to determine the corresponding rules, and then match the class name with the actual disk file in the autoload() function to achieve the effect of lazy loading. From here we can also see the effect of autoload(). The most important thing in the implementation of the function is the implementation of the mapping rules between the class name and the actual disk file.
But now the problem comes, if in the implementation of a system, if many other class libraries need to be used, these Class libraries may be written by different developers, and the mapping rules between their class names and actual disk files are different. At this time, if you want to implement automatic loading of the class library file, you must use autoload(. ) function, the autoload() function may be very complicated or even impossible to implement. In the end, the autoload() function may be very bloated. Even if it can be implemented, it will hinder future maintenance. and system efficiency will have a great negative impact. In this case, isn't there a simpler and clearer solution? The answer is of course: NO! Before looking at further solutions, let's take a look at PHP. How is the autoload mechanism implemented?
(2) Implementation of PHP's autoload mechanism
We know that the execution of PHP files is divided into two independent In the process, the first step is to compile the PHP file into a bytecode sequence commonly called OPCODE (actually compiled into a byte array called zend_op_array), and the second step is to execute these OPCODEs by a virtual machine. All behaviors of PHP are implemented by these OPCODEs. Therefore, in order to study the implementation mechanism of autoload in PHP, we compile the autoload.php file into opcode, and then study what PHP does in the process based on these OPCODEs. :
/* autoload.php The compiled OPCODE list uses the OPDUMP tool developed by the author
*/
The code is as follows:
<?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="扩展名">扩展名。
How to make spl_autoload work automatically, that is, point autoload_func to spl_autoload? The answer is to use the spl_autoload_register function. By calling spl_autoload_register() for the first time in a PHP script without any parameters, you can point autoload_func to spl_autoload.
Through the above description, we know that the function of spl_autoload is relatively simple, and it is implemented in the SPL extension, and we cannot expand its function. What if you want to implement your own more flexible automatic loading mechanism? At this time, the spl_autoload_call function makes its debut.
Let’s first take a look at the wonderful features of the implementation of spl_autoload_call. Inside the SPL module, there is a global variable autoload_functions, which is essentially a HashTable, but we can simply think of it as a linked list. Each element in the linked list is a function pointer, pointing to a function with autoloading Function of class function. The implementation of spl_autoload_call itself is very simple. It simply executes each function in the linked list in order. After each function is executed, it is judged whether the required class has been loaded. If the loading is successful, it returns directly and does not continue to execute the linked list. other functions. If the class has not been loaded after all functions in this linked list have been executed, spl_autoload_call will exit directly without reporting an error to the user. Therefore, using the autoload mechanism does not guarantee that the class will be automatically loaded correctly. The key still depends on how your autoloading function is implemented.
So who maintains the automatic loading function list autoload_functions? It is the spl_autoload_register function mentioned earlier. It can register the user-defined autoloading function into this linked list, and point the autoload_func function pointer to the spl_autoload_call function (note that there is an exception, and the specific situation is left to everyone to think about). We can also delete registered functions from the autoload_functions linked list through the spl_autoload_unregister function.
As mentioned in the previous section, when the autoload_func pointer is non-null, the autoload() function will not be automatically executed. Now autoload_func has pointed to spl_autoload_call. What should we do if we still want the autoload() function to work? Woolen cloth? Of course, still use the spl_autoload_register(autoload) call to register it in the autoload_functions linked list.
Now back to the last question in the first section, we have a solution: implement their own autoloading functions according to the different naming mechanisms of each class library, and then use spl_autoload_register to register them to the SPL autoloading function respectively. Just put it in the queue. This way we don't have to maintain a very complex autoload function.
(4) Autoload efficiency issues and countermeasures
When using the autoload mechanism, many people's first reaction is that using autoload will reduce system efficiency. Some people even suggest not to use autoload for the sake of efficiency. After we understand the principle of autoload implementation, we know that the autoload mechanism itself is not the reason for affecting system efficiency. It may even improve system efficiency because it will not load unnecessary classes into the system.
So why do many people have the impression that using autoload will reduce system efficiency? In fact, it is precisely the user-designed autoloading function that affects the efficiency of the autoload mechanism. If it cannot efficiently match the class name to the actual disk file (note, this refers to the actual disk file, not just the file name), the system will have to do a lot of file existence verification (requiring in each include path (to search in the path included in the file), and determining whether the file exists requires disk I/O operations. As we all know, the efficiency of disk I/O operations is very low, so this is the culprit that reduces the efficiency of the autoload mechanism!
Therefore, when we design the system, we need to define a clear mechanism for mapping class names to actual disk files. The simpler and clearer this rule is, the more efficient the autoload mechanism will be.
Conclusion: The autoload mechanism is not inherently inefficient. Only abuse of autoload and poorly designed autoloading functions will lead to a reduction in its efficiency.
The above is the detailed content of Comparative analysis of autoload and spl_autoload automatic loading. For more information, please follow other related articles on the PHP Chinese website!