PHP自动加载机制

概述

首先,为什么PHP需要自动加载呢?

在PHP面向对象(OO)编程中,为了方便管理,我们都会把一个类写在一个单独的文件中,那么如果想在A类中使用B类的功能,就需要把B类加载到A类。对于这样的需求在最原始的时候,我们是通过require 和 include 语法实现将文件加载到另一个文件中,include 和 require 是PHP中引入文件的两个基本方法。

在小规模开发中直接使用 include 和 require 不会有什么不妥,但在大型项目中会造成大量的 include 和 require 堆积。这样的代码既不优雅,执行效率也很低,而且维护起来也相当困难。

PHP的自动加载功能,框架实现自动加载的包括PHP规范中的PSR0和PSR4原则,Composer的自动加载功能等等。

php加载文件方式

  1. 常规加载:include,include_once,requice,requice_one
  2. 魔法方法:__autoload()
  3. SPL 自动加载:spl_autoload_register()

include()与require()

简单的文件加载方法:

  1. require()

    包含的意思,找不到文件时,会报warning的错误,然后程序继续往下执行。include语句只有在被执行时才会读入要包含的文件。在错误处理方便,使用include语句,如果发生包含错误,程序将跳过include语句,虽然会显示错误信息但是程序还是会继续执行!php处理器会在每次遇到include()语句时,对它进行重新处理,所以可以根据不同情况的,在条件控制语句和循环语句中使用include()来包含不同的文件。

  2. include()

    必须的意思,找不到文件时,会报fatal error(致命错误),程序停止往下执行。在php文件被执行之前,php解析器会用被引用的文件的全部内容替换require语句,然后与require语句之外的其他语句组成个新的php文件,最好后按新的php文件执行程序代码。

  3. require_once()

    类似于include(),系统会进行判断,如果已经包含,则不会再包含第二次。

  4. include_once()

    类似于require(),系统会进行判断,如果已经包含,则不会再包含第二次。

共同点:能包含位于独立文件中的代码,可以减少代码的重复,实现代码结构的模块化,方便调用。

  • 注意事项

    • 加载文件格式

      include/require 包含进来的文件必须要加<?php ?>因为在包含时,首先理解文件内容是普通字符串,碰到<?php ?>标签时,才去解释。

    • 路径要求

      可以用绝对路径,也可以用相对路径;windows下正反斜线都可以,linux下只认正斜线,所以最好用正斜线。

    • 如何选择

      比如是系统配置,缺少了,网站不让运行,自然用require,如果是某一段统计程序,少了,对网站只是少统计人数罢了,不是必须要的,可以用include而加不加once是效率上的区别,加上once,虽然系统帮你考虑了只加载一次,但系统的判断会是效率降低,因此,更应该在开发之初,就把目录结构调整好,尽量不要用_once的情况。

    • 特殊用法

      利用include/require返回被包含页面的返回值

      1
      2
      a.php页面中: ..... return $value; 
      b.php页面中:$v = include("a.php");

__autoload()自动加载

  • PHP5及之后的版本,使用尚未定义的类时会自动调用__autoload函数,所以我们可以通过编写__autoload函数来让php自动加载类,而不必写一个长长的包含文件列表。

    需明确的是对于__autoload()函数,PHP在找不到类的时候会自动执行,但是PHP内部并没有定义这个函数。

    这个函数需要开发者自定义,并且编写内部逻辑,PHP只负责在需要的时候自动调用执行。而且在调用的时候会自动传人要加载的类名作为参数。

  • 用法:首先需要在需要加载文件的代码中,定义__autoload()函数,并且编写内部逻辑。PHP在找不到类的时候会自动执行__autoload()函数。下面是A.php需要加载B.php的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //文件 B.php 不做修改
    //文件 A.php
    <?php
    class A{
    public function test(){
    $b_object = new B();
    $b_object->echo_info();
    }
    }
    function __autoload($classname){
    require $classname.'.php';//include 'b.php';
    }
    $a_object = new A();
    $a_oject->test();
    ?>
    命令行输入:#php a.php
    输出: “我是class B中的方法执行结果“
  • 缺陷:一个项目中仅能有一个这样的 __autoload() 函数,因为 PHP 不允许函数重名,也就是说你不能声明2个__autoload()函数文件,否则会报致命错误。

    如果项目比较大,加载每个文件都使用同样的规则显然是不现实的,那么我们可能就需要在__autoload()中编写复杂的规则逻辑来满足加载不同文件的需求。

    这同样会使得__autoload()函数变得复杂臃肿,难以维护管理

SPL 自动加载

PHP在实例化一个对象时(实际上在实现接口,使用类常数或类中的静态变量,调用类中的静态方法时都会如此),首先会在系统中查找该类(或接口)是否存在,如果不存在的话就尝试使用autoload机制来加载该类。而autoload机制的主要执行过程为:

  • 检查执行器全局变量函数指针autoload_func是否是NULL;
  • 如果 autoload_func==NULL ,则查找系统是否定义 __autoload() 函数,如果定义了,则执行并返回加载结果。如果没有定义,则报错并退出;
  • 如果 autoload_func 不等于NULL,则直接执行 autoload_func 指向的函数加载类,此时并不检查 __autoload() 函数是否定义。

通过上述PHP自动加载流程,可知PHP实际上提供了两种方法来实现自动装载机制:(1)使用用户定义的__autoload()函数,这通常在PHP源程序中来实现;(2)设计一个函数,将autoload_func指针指向它,这通常使用C语言在PHP扩展中实现,即 SPL autoload机制,即本节中的SPL自动加载。如果两种方式都实现了,也就是 autoload_func 不等于NULL,程序只会执行第二种方式,__autoload() 函数是不会被执行的。

  • 用法

    通过spl_autoload_register('my_autoload'),实现了 当程序执行找不到类B时,会执行 自定义的 my_autoload()函数,加载B类。实际上 spl_autoload_register('my_autoload') 的作用就是 把autoload_func 指针指向 my_autoload()

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    B.php文件不变
    A.php
    <?php
    class A{
    public function test(){
    $b_object = new B();
    $b_object->echo_info();
    }
    }

    function __autoload($classname){
    require $classname.'.php';//include 'b.php';
    }

    function my_autoload($classname){
    require $classname.'.php';//include 'b.php';
    echo 'my_autoload ';
    }

    spl_autoload_register('my_autoload');
    $a_object = new A();
    $a_object->test();

    结果:my_autoload 我是class B中的方法执行结果
    ?>
  • SPL 自动加载整个过程

    针对上述的示例,假如把spl_autoload_register('my_autoload')改成 spl_autoload_register()不添加任何参数,B类也能被加载。

    为什么呢?

    因为SPL扩展内部自己定义了一个自动加载函数 spl_autoload(),实现了自动加载的功能,如果我们不定义自己的自动加载函数,并且程序里写了spl_autoload_register()(如果不传参数,必须是第一次执行才会有效)或者 spl_autoload_register('spl_autoload'),那么autoload_func 指针就会指向内部函数 spl_autoload()。程序执行的时候如果找不到相应类就会执行该自动加载函数。

    SPL 是怎么实现autoload_func 指针指向不同的函数呢?

    在SPL内部定义了 一个函数 spl_autoload_call() 和 一个全局变量autoload_functionsautoload_functions本质上是一个HashTable,不过我们可以将其简单的看作一个链表,链表中的每一个元素都是一个函数指针,指向一个具有自动加载类功能的函数。

    spl_autoload_call()的作用就是按顺序遍历 autoload_functions,使得autoload_func指向每个自动加载函数,如果加载成功就停止,如果不成功就继续遍历下个自动加载函数,直到加载成功或者遍历完所有的函数。

    autoload_functions 这个列表是谁来维护的呢?

    spl_autoload_register() 这个函数维护。我们说的自动加载函数的注册,其实就是通过·spl_autoload_register()·把自动加载函数加入到autoload_functions 列表。

    相关SPL自动加载函数

    1
    2
    spl_autoload_functions() //打印autoload_functions列表
    spl_autoload_unregister() //注销自动加载函数

参考:

  1. PHP-自动加载原理分析
  2. php自动加载方式集合