Before the magic function __autoload() method appeared in PHP, if you wanted to instantiate 100 objects in a program file, then you must include or require to include 100 class files, or you define these 100 classes in the same class file - I believe this file will definitely be very large. However, the __autoload() method has come out, so there is no need to worry about it in the future. This class will automatically load the established file before you instantiate the object.
1. Overview of the autoload mechanism
When developing systems using PHP's OO mode, it is usually accustomed to storing the implementation of each class in a separate file, which will easily reuse the classes and be 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 just need to include it directly using include/require. Here is a practical example:
/* */ <?php class Person { var $name, $age; function __construct ($name, $age) { $this->name = $name; $this->age = $age; } } ?> /* no_autoload.php */ <?php require_once (””); $person = new Person(”Altair”, 6); var_dump ($person); ?>
In this example, the file needs to use the Person class, which uses require_once to include it, and then you can use the Person class to instantiate an object directly.
However, as the project scale continues to expand, using this method will bring some implicit problems: if a PHP file needs to use many other classes, then a lot of require/include statements are required, which may cause omissions or include unnecessary class files. If a large number of files need to use other classes, it is definitely a nightmare to ensure that each file contains the correct class files.
PHP5 provides a solution to this problem, which is the autoload mechanism of the class. The autoload mechanism makes it possible for PHP programs to automatically include class files when using classes, instead of including all class files at the beginning. This mechanism is also called lazy loading.
Here is an example of using the autoload mechanism to load Person class:
/* */ <?php function __autoload($classname) { $classpath="./".$classname.'.'; if(file_exists($classpath)) { require_once($classpath); } else { echo 'class file'.$classpath.'not found!'; } } $person = new Person(”Altair”, 6); var_dump ($person); ?>
Usually when PHP5 uses a class, if it finds that the class is not loaded, it will automatically run the __autoload() function, in which we can load the class we need to use. In our simple example, we directly add the class name to the extension "." to form the class file name, and then load it using require_once. 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, the second thing is to determine the disk path where the class file is located (in our example, the simplest case, the class is in the same folder as the PHP program file that calls them), and the third thing is to load the class from the disk file into the system. The third step is the simplest, just use include/require. To implement the functions of the first and second steps, we must agree on the mapping method of the class name and disk file during development. Only in this way can we find the corresponding disk file based on the class name.
Therefore, when there are a large number of class files to include, we can just determine the corresponding rules and then correspond to 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 that the most important thing in the implementation of the __autoload() function is the implementation of the class name and the actual disk file mapping rules.
But now the problem is that if in an implementation of a system, if many other class libraries are needed, these class libraries may be written by different developers, and their class names are not the same as the mapping rules of the actual disk files. At this time, if you want to automatically load the class library file, you must implement all mapping rules in the __autoload() function. In this way, the __autoload() function may be very complicated and even impossible to implement. In the end, it may cause the __autoload() function to be very bloated. Even if it can be implemented, it will have a great negative impact on future maintenance and system efficiency. In this case, is there no simpler and clearer solution? The answer is of course: NO! Before looking at the further solution, let’s take a look at how the autoload mechanism in PHP is implemented.
2. Implementation of PHP's autoload mechanism
We know that the execution of PHP files is divided into two independent processes. 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 file into opcode, and then study what PHP does in this process based on these OPCODEs:
/* The compiled OPCODE list is an OPDUMP tool developed by the author. * The generated results can be downloaded to the website / to download the software. */ 1: <?php 2: // require_once (””); 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. “.”); 5 CONCAT !0, ‘.' =>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: ?>
In line 10 of the code we need to instantiate an object for class Person. Therefore, the autoload mechanism will definitely be reflected in the compiled opcode of this line. From the OPCODE generated by line 10 above, we know that when instantiating the object Person, the FETCH_CLASS instruction must be executed first. We start our exploration journey from PHP's processing of the FETCH_CLASS instruction.
By looking up the source code of PHP (I am using PHP 5.3alpha2 version) you can find the following call sequence:
ZEND_VM_HANDLER(109, ZEND_FETCH_CLASS, …) (zend_vm_def.h Line 1864)
=> zend_fetch_class (zend_execute_API.c line 1434)
=>zend_lookup_class_ex (zend_execute_API.c line 964)
=> zend_call_function(&fcall_info, &fcall_cache) (zend_execute_API.c Line 1040)
Before the last step of calling, let's take a look at the key parameters when calling:
/* Set the value of the autoload_function variable to "__autoload" */
fcall_info.function_name = &autoload_function; // Ooops, I finally found "__autoload"
…
fcall_cache.function_handler = EG(autoload_func); // autoload_func !
zend_call_function is one of the most important functions in Zend Engine. Its main function is to execute functions customized by users in PHP programs or PHP's own library functions. zend_call_function has two important pointer parameters fcall_info, fcall_cache, which point to two important structures, one is zend_fcall_info, and the other is zend_fcall_info_cache. The main workflow of zend_call_function is as follows: If the pointer of fcall_cache.function_handler is NULL, try to find a function with the function named fcall_info.function_name, and if it exists, execute it; if fcall_cache.function_handler is not NULL, directly execute the function pointed to by fcall_cache.function_handler.
Now we know that when PHP instantiates an object (in fact, when implementing an interface, using class constants or static variables in the class, and calling static methods in the class), it will first look for whether the class (or interface) exists in the system, and if it does not exist, try to use the autoload mechanism to load the class. The main execution process of the autoload mechanism is:
- Check whether the executor global variable function pointer autoload_func is NULL.
- If autoload_func==NULL, then find whether the __autoload() function is defined in the system. If not, an error is reported and exited.
- If the __autoload() function is defined, then the __autoload() is executed to try to load the class and return the load result.
- If autoload_func is not NULL, directly execute the function pointed to by the autoload_func pointer to load the class. Note that at this time, it is not checked whether the __autoload() function is defined.
The truth is finally revealed. PHP provides two methods to implement the automatic loading mechanism. One we have mentioned earlier is to use the user-defined __autoload() function, which is usually implemented in the PHP source program; the other is to design a function and point the autoload_func pointer to it, which is usually implemented in PHP extensions using C language. If both the __autoload() function is implemented and the autoload_func (pointing autoload_func to a certain PHP function), then only the autoload_func function is executed.
3. Implementation of SPL autoload mechanism
SPL is the abbreviation of Standard PHP Library. It is an extension library introduced by PHP5. Its main functions include the implementation of the autoload mechanism and various Iterator interfaces or classes. The implementation of the SPL autoload mechanism is implemented by pointing the function pointer autoload_func to a function that it implements with the automatic loading function. SPL has two different functions, spl_autoload, spl_autoload_call, which implements different autoload mechanisms by pointing autoload_func to these two different function addresses.
spl_autoload is the default automatic loading function implemented by SPL, and its functions are relatively simple. It can receive two parameters. The first parameter is $class_name, which represents the class name. The second parameter $file_extensions is optional, representing the extension of the class file. You can specify multiple extensions in $file_extensions, and separate the exhibition names with semicolons; if not specified, it will use the default extension .inc or .php. spl_autoload first turns $class_name to lowercase, and then searches all include paths for $class_name.inc or $class_name.php files (if the $file_extensions parameter is not specified), and if found, loads the file of this class. You can manually load the Person class using spl_autoload("Person", "."). In fact, it is similar to require/include, and it can specify multiple extensions for different purposes.
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. When you call spl_autoload_register() for the first time in PHP script, you can point autoload_func to spl_autoload without using any parameters.
Through the above instructions, we know that the function of spl_autoload is relatively simple, and it is implemented in SPL extensions, so we cannot expand its functions. What if you want to implement your own more flexible automatic loading mechanism? At this time, the spl_autoload_call function made its debut.
Let's first look at the wonderfulness 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 regard it as a linked list. Each element in the linked list is a function pointer, pointing to a function with the function of automatically loading the class. 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 determined whether the required class has been loaded once. If it is loaded successfully, it will be returned directly and no longer continue to execute other functions in the linked list. If all functions in this linked list are executed and the class has not been loaded, spl_autoload_call will exit directly and will not report errors to the user. Therefore, using the autoload mechanism does not guarantee that the class will be automatically loaded correctly. The key is to see how your automatic loading function is implemented.
So who maintains the autoload_functions list of autoload list? It is the spl_autoload_register function mentioned earlier. It can register the user-defined automatic loading function into this linked list and point the autoload_func function pointer to the spl_autoload_call function (note that there is an exception in one case, which is left for 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 not empty, the __autoload() function will not be automatically executed. Now the autoload_func has pointed to spl_autoload_call. What should we do if we still want the __autoload() function to work? Of course, it is still used to register it in the autoload_functions linked list using the spl_autoload_register(__autoload) call.
Now back to the last problem in the first section, we have a solution: implement their own automatic loading functions according to the different naming mechanisms of each class library, and then register them in the SPL automatic loading function queue using spl_autoload_register. In 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, and some even suggest not to use autoload for efficiency. After we understand the principles of autoload implementation, we know that the autoload mechanism itself is not a reason for affecting system efficiency, and it is even possible to improve system efficiency because it does not load unwanted classes into the system.
So why do many people have the impression that using autoload will reduce system efficiency? In fact, affecting the efficiency of the autoload mechanism itself is the automatic loading function designed by the user. If it cannot efficiently correspond to the class name with the actual disk file (note that the actual disk file here, not just the file name), the system will have to judge whether a large number of files exist (it needs to be found in the path included in each include path), and to determine whether the file exists, disk I/O operations are required. As we all know, disk I/O operations are very low, so this is the culprit that reduces the efficiency of the autoload mechanism!
Therefore, when designing the system, we need to define a clear mechanism for mapping class names with actual disk files. The simpler and clearer this rule is, the more efficient the autoload mechanism is.
Conclusion: The autoload mechanism is not naturally inefficient. Only by abuse of autoload and poorly designed autoloading functions will lead to a reduction in its efficiency.
The above is the information sorting out the PHP autoload mechanism. We will continue to add relevant information in the future. Thank you for your support to this site!