Home >Backend Development >PHP Tutorial >Detailed analysis of the autoload mechanism in php

Detailed analysis of the autoload mechanism in php

不言
不言Original
2018-09-07 14:21:277860browse

__autoload implements automatic loading; however, due to the introduction of multi-class libraries, __autoload maintenance will be complicated, so spl_aotoload is introduced. spl implements the manual registration and removal of an automatically loaded function list. Let’s take a look at the specific content. .

PHP Before the magic function __autoload() method appeared, if you wanted to instantiate 100 objects in a program file, then you had to use include or require to include 100 class files, or you 100 classes are defined in the same class file - I believe this file will be very large. But with the __autoload() method, you don't have to worry about it in the future. This class will automatically load the specified file before you instantiate the object.

1. Overview of the autoload mechanism

When using PHP's OO mode to develop a system, it is usually customary to store the implementation of each class in a separate file, which will be very easy. It enables the reuse of classes and makes it easy to maintain in the future. 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:

/* 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 to include it, and then the Person class can be used directly 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 from the beginning. This mechanism is also called lazy loading.

The following is an example of using the autoload mechanism to load the Person class:

/* autoload.php */
<?php
 function __autoload($classname)
{
  $classpath="./".$classname.&#39;.class.php&#39;;
  if(file_exists($classpath))
  {
    require_once($classpath);
  }
  else
  {
    echo &#39;class file&#39;.$classpath.&#39;not found!&#39;;
   }
}
 
 $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 this function we can load the classes we need to use. In our simple example, we directly add the class name with the 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 must 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 where the class file is located. path (in our case the simplest case, the classes are in the same folder as the PHP program file that calls them), and the third thing is to load the classes into the system from the disk file. The third step is the simplest, just use include/require. To realize the functions of the first and second steps, the mapping method between the class name and the disk file must be agreed upon 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 class files to be included, we only need to determine the corresponding rules, and then in the __autoload() function, match the class name with the actual disk file to achieve lazy loading effect. From here we can also see that the most important thing in the implementation of the __autoload() function is the implementation of the mapping rules between the class name and the actual disk file.

But now comes the problem. If in the implementation of a system, if you need to use many other class libraries, these class libraries may be written by different developers, and their class names are different from the actual disk files. The mapping rules are different. At this time, if you want to implement automatic loading of class library files, you must implement all mapping rules in the __autoload() function. In this case, the __autoload() function may be very complicated or even impossible to implement. In the end, the __autoload() function may become very bloated. Even if it can be implemented, it will have a great negative impact on future maintenance and system efficiency. In this case, isn't there a simpler and clearer solution? The answer is of course: NO! Before looking at further solutions, let's first 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 a PHP file is divided into two independent processes. The first step is to compile the PHP file into what is commonly known as ## The bytecode sequence of #OPCODE (actually compiled into a byte array called zend_op_array), 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 compiled the autoload.php file into opcode, and then used these OPCODEs to study what PHP did in the process:

 /* 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机制的主要执行过程为:

  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是可选的,表示类文件的扩展名,可以在$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函数闪亮登场了。

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 that has the function of automatically loading classes. 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. If we still want the __autoload() function to work, it should How to do it? 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 complicated __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, what affects the efficiency of the autoload mechanism itself is precisely the autoloading function designed by the user. 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 autoload functions will lead to a reduction in its efficiency.

Related recommendations:

PHP autoload running mechanism example analysis, autoload example analysis

##PHP autoload mechanism (lazy loading )


The above is the detailed content of Detailed analysis of the autoload mechanism in php. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn