>백엔드 개발 >PHP 튜토리얼 >PHP의 자동 로드 메커니즘에 대한 자세한 분석

PHP의 자동 로드 메커니즘에 대한 자세한 분석

不言
不言원래의
2018-09-07 14:21:277824검색

__autoload는 자동 로딩을 구현합니다. 그러나 여러 클래스 라이브러리의 도입으로 인해 __autoload 유지 관리가 복잡해지기 때문에 spl_aotoload는 자동 로딩 기능 목록의 수동 등록 및 제거를 구현합니다.

PHP 마법 함수 __autoload() 메소드가 등장하기 전에 프로그램 파일에서 100개의 객체를 인스턴스화하려면 include 또는 require를 사용하여 100개의 클래스 파일을 포함해야 했거나 동일한 클래스 파일에서 이 100개의 클래스를 정의했습니다. 나는 이 파일이 매우 클 것이라고 믿습니다. 그러나 __autoload() 메서드를 사용하면 나중에 이에 대해 걱정할 필요가 없습니다. 이 클래스는 객체를 인스턴스화하기 전에 자동으로 지정된 파일을 로드합니다.

1. 자동 로드 메커니즘 개요

PHP의 OO 모드를 사용하여 시스템을 개발할 때 일반적으로 각 클래스의 구현을 별도의 파일에 저장하여 클래스를 쉽게 재사용할 수 있습니다. 향후 유지 관리에 편리합니다. 이는 OO 디자인의 기본 아이디어 중 하나이기도 합니다. PHP5 이전에는 클래스를 사용해야 하는 경우 include/require를 사용하여 직접 포함하기만 하면 되었습니다. 실제 예는 다음과 같습니다.

/* 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);
?>

이 예에서 no-autoload.php 파일은 이를 포함하기 위해 require_once를 사용하는 Person 클래스를 사용해야 하며 그런 다음 Person 클래스를 직접 사용하여 객체를 인스턴스화할 수 있습니다.

그러나 프로젝트 규모가 계속 확장됨에 따라 이 방법을 사용하면 몇 가지 숨겨진 문제가 발생합니다. PHP 파일이 다른 많은 클래스를 사용해야 하는 경우 많은 require/include 문이 필요하므로 누락이 발생할 수 있습니다. 불필요한 클래스 파일을 포함합니다. 많은 수의 파일에 다른 클래스를 사용해야 하는 경우 각 파일에 올바른 클래스 파일이 포함되어 있는지 확인하는 것은 악몽이 될 것입니다.

PHP5는 클래스의 자동 로딩(autoload) 메커니즘으로 이 문제에 대한 해결책을 제공합니다. 자동 로드 메커니즘을 사용하면 처음부터 모든 클래스 파일을 포함하는 대신 PHP 프로그램이 클래스를 사용할 때만 클래스 파일을 자동으로 포함할 수 있습니다. 이 메커니즘을 지연 로딩이라고도 합니다.

다음은 자동 로드 메커니즘을 사용하여 Person 클래스를 로드하는 예입니다.

/* 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);
 ?>

보통 PHP5가 클래스를 사용할 때 클래스가 로드되지 않은 것을 발견하면 자동으로 __autoload() 함수가 실행됩니다. 함수를 사용하면 필요한 클래스를 로드할 수 있습니다. 간단한 예에서는 ".class.php" 확장자를 사용하여 클래스 이름을 직접 추가하여 클래스 파일 이름을 구성한 다음 require_once를 사용하여 로드합니다. 이 예를 보면 autoload는 최소한 세 가지 작업을 수행해야 함을 알 수 있습니다. 첫 번째는 클래스 이름을 기준으로 클래스 파일 이름을 결정하고, 두 번째는 클래스 파일이 있는 디스크 경로를 결정하는 것입니다. (이 예에서는 가장 간단한 경우이며 클래스는 클래스를 호출하는 PHP 프로그램 파일과 동일한 폴더에 있습니다.) 세 번째는 클래스를 디스크 파일에서 시스템으로 로드하는 것입니다. 세 번째 단계는 가장 간단합니다. include/require를 사용하면 됩니다. 첫 번째와 두 번째 단계의 기능을 구현하려면 개발 중에 클래스 이름과 디스크 파일 간의 매핑 방법을 합의해야 합니다. 이 방법을 통해서만 클래스 이름을 기반으로 해당 디스크 파일을 찾을 수 있습니다.

따라서 포함할 클래스 파일 수가 많은 경우 해당 규칙을 결정한 다음 __autoload() 함수에서 클래스 이름을 실제 디스크 파일과 일치시키기만 하면 지연 로딩 효과를 얻을 수 있습니다. 여기서도 __autoload() 함수 구현에서 가장 중요한 것은 클래스 이름과 실제 디스크 파일 간의 매핑 규칙 구현이라는 것을 알 수 있습니다.

하지만 이제 문제가 발생합니다. 시스템 구현에서 다른 많은 클래스 라이브러리를 사용해야 하는 경우 이러한 클래스 라이브러리는 다른 개발자가 작성할 수 있으며 클래스 이름과 실제 디스크 파일 간의 매핑 규칙이 모두 다릅니다. 동일합니다. 이때, 클래스 라이브러리 파일의 자동 로딩을 구현하려면 __autoload() 함수에서 모든 매핑 규칙을 구현해야 합니다. 이 경우 __autoload() 함수는 구현하기가 매우 복잡하거나 불가능할 수도 있습니다. 결국 __autoload() 함수는 매우 비대해질 수 있습니다. 구현이 가능하더라도 향후 유지 관리 및 시스템 효율성에 큰 부정적인 영향을 미칠 것입니다. 이런 경우에는 더 간단하고 명확한 해결책은 없을까요? 대답은 물론입니다: 아니요! 추가 솔루션을 살펴보기 전에 먼저 PHP의 자동 로드 메커니즘이 어떻게 구현되는지 살펴보겠습니다.

2. PHP 자동 로드 메커니즘 구현

우리는 PHP 파일의 실행이 두 개의 독립적인 프로세스로 나누어져 있다는 것을 알고 있습니다. 첫 번째 단계는 PHP 파일을 일반적으로 OPCODE라고 하는 바이트 코드 시퀀스로 컴파일하는 것입니다. 이를 zend_op_array라는 바이트 배열로 컴파일하고 두 번째 단계는 가상 머신에서 이러한 OPCODE를 실행하는 것입니다. PHP의 모든 동작은 이러한 OPCODE로 구현됩니다. 따라서 PHP의 자동 로드 구현 메커니즘을 연구하기 위해 autoload.php 파일을 opcode로 컴파일한 다음 이러한 OPCODE를 사용하여 PHP가 프로세스에서 수행한 작업을 연구했습니다.

 /* 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函数闪亮登场了。

먼저 spl_autoload_call 구현의 놀라운 기능을 살펴보겠습니다. SPL 모듈 내부에는 본질적으로 HashTable인 전역 변수 autoload_functions가 있지만 간단히 연결 목록으로 생각할 수 있습니다. 연결 목록의 각 요소는 다음과 같은 기능을 가진 함수를 가리키는 함수 포인터입니다. 자동으로 클래스를 로드하는 기능입니다. spl_autoload_call 구현 자체는 매우 간단하며, 단순히 연결 리스트에 있는 각 함수를 순서대로 실행한 후, 필요한 클래스가 로드되었는지 판단하여 로딩에 성공하면 바로 반환을 하게 됩니다. 연결된 목록의 다른 기능을 계속 실행합니다. 이 연결된 목록의 모든 함수가 실행된 후에도 클래스가 로드되지 않은 경우 spl_autoload_call은 사용자에게 오류를 보고하지 않고 직접 종료됩니다. 따라서 자동 로드 메커니즘을 사용한다고 해서 클래스가 자동으로 올바르게 로드되는 것은 아닙니다. 핵심은 여전히 ​​자동 로드 기능이 구현되는 방식에 따라 달라집니다.

그럼 자동 로딩 기능 목록 autoload_functions는 누가 관리하나요? 앞서 언급한 spl_autoload_register 함수입니다. 이 연결 목록에 사용자 정의 자동 로딩 함수를 등록하고 autoload_func 함수 포인터를 spl_autoload_call 함수에 지정할 수 있습니다(예외가 있으며 특정 상황은 모두가 생각하도록 남겨둡니다). spl_autoload_unregister 함수를 통해 autoload_functions 연결 목록에서 등록된 함수를 삭제할 수도 있습니다.

이전 섹션에서 언급했듯이 autoload_func 포인터가 null이 아니면 __autoload() 함수가 자동으로 실행되지 않습니다. 이제 autoload_func가 spl_autoload_call을 가리켰습니다. 일하다? 물론, 여전히 spl_autoload_register(__autoload) 호출을 사용하여 autoload_functions 연결 목록에 등록합니다.

이제 첫 번째 섹션의 마지막 질문으로 돌아가서 해결책이 있습니다. 각 클래스 라이브러리의 다양한 명명 메커니즘에 따라 자체 자동 로딩 기능을 구현한 다음 spl_autoload_register를 사용하여 SPL 자동 로딩 기능에 등록합니다. 대기줄. . 이렇게 하면 매우 복잡한 __autoload 기능을 유지할 필요가 없습니다.

4. 자동 로드 효율성 문제 및 대책

자동 로드 메커니즘을 사용할 때 많은 사람들의 첫 번째 반응은 자동 로드를 사용하면 시스템 효율성이 저하된다는 것입니다. 어떤 사람들은 효율성을 위해 자동 로드를 사용하지 말라고 제안하기도 합니다. 자동 로드 구현의 원리를 이해한 후에는 자동 로드 메커니즘 자체가 시스템 효율성에 영향을 미치는 이유가 아니라는 것을 알게 됩니다. 자동 로드는 불필요한 클래스를 시스템에 로드하지 않기 때문에 시스템 효율성을 향상시킬 수도 있습니다.

그렇다면 왜 많은 사람들은 자동 로드를 사용하면 시스템 효율성이 떨어진다고 생각할까요? 실제로 자동 로드 메커니즘의 효율성에 영향을 미치는 것은 바로 사용자가 설계한 자동 로드 기능입니다. 클래스 이름을 실제 디스크 파일과 효율적으로 일치시킬 수 없는 경우(참고: 이는 파일 이름뿐만 아니라 실제 디스크 파일을 나타냄) 시스템은 많은 파일 존재 확인을 수행해야 합니다(각 포함 경로( 파일에 포함된 경로를 검색하려면), 파일이 있는지 확인하려면 디스크 I/O 작업이 필요합니다. 우리 모두 알고 있듯이 디스크 I/O 작업의 효율성이 매우 낮아 효율성을 저하시키는 원인입니다. 자동 로드 메커니즘!

따라서 시스템을 설계할 때 클래스 이름을 실제 디스크 파일에 매핑하기 위한 명확한 메커니즘을 정의해야 합니다. 이 규칙이 더 간단하고 명확할수록 자동 로드 메커니즘은 더 효율적입니다.

결론: 자동 로드 메커니즘은 본질적으로 비효율적이지 않습니다. 자동 로드의 남용과 잘못 설계된 자동 로드 기능만이 효율성 감소로 이어질 것입니다.

관련 권장 사항:

PHP의 자동 로드 작동 메커니즘 예시 분석, 자동 로드 예시 분석

PHP 자동 로드 메커니즘(지연 로딩)


위 내용은 PHP의 자동 로드 메커니즘에 대한 자세한 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.