当前位置 : 主页 > 网络编程 > PHP >

spl_autoload_register的执行原理

来源:互联网 收集:自由互联 发布时间:2023-09-03
应用场景 在PHP中,当我们想要使用其它文件时,需要先引入文件,这时候我们会使用include或者require引入。当我们的项目只有几个文件时,使用这种方式手动引入,就很nice,但是现在的
应用场景

在PHP中,当我们想要使用其它文件时,需要先引入文件,这时候我们会使用include或者require引入。当我们的项目只有几个文件时,使用这种方式手动引入,就很nice,但是现在的项目都是使用框架,动辄几百上千个文件,如果还是使用这种方法引入的话,就会出现以下问题:

  • 文件刚打开,发现头部好多include,很不美观
  • 有时候不确定某个文件是否引入了,还得先调试代码,发现报错了,再手动引入,这种开发模式就很不友好

所以有没有解决这种问题的方法,可以自动引入呢?答案当然是有了,PHP在SPL标准库中给我们内置了今天的主角,也就是spl_autoload_register函数。

怎么用
定义:将给定函数注册为 __autoload() 实现,即自动加载函数
function spl_autoload_register(?callable $callback, bool $throw = true, bool $prepend = false): bool {}

该函数接受三个参数,

  1. 闭包函数的名字,也可以是类的方法,也可以是闭包
  2. 是否应在 无法注册 时抛出异常 默认为true,从 PHP 8.0.0 开始,该参数被忽略,spl_autoload_register() 现在总是会在无效参数上 抛出TypeError 。
  3. 是否将注册函数放到队列的头部,默认false,追加到队列的尾部,设置为true,追加到队列头部
详解

为了更好的理解该函数,我们现在创建一个测试demo

A.php

<?php
class A{

    public static function test()
    {
        echo 'a';
    }

}

B.php

<?php

class B{
    public static function test()
    {
        echo 'b';
    }
}

index.php

<?php
include './a.php';
A::test();

  访问index.php 就会输出a,这是我们正常引入文件,使用上面的方式,但是我们今天的主角是spl_autoload_register函数,所以我们修改一下index.php

<?php
	function loaderA(){
  		 var_dump(1111);
        include './A.php';
  }
spl_autoload_register('loaderA',true,true);
A::test();

此时访问index.php,会输出 如下

int(1111)
a

现在我们解读一下上面的代码,在第6行代码我们将loaderA函数注册为一个自动加载函数,当运行到第7行代码的时候,发现找不到类A,但是此时不会直接报错,系统监测到我们已经提前注册了spl_autoload_register函数,所以,此时代码走到第2行,调用loaderA函数,就输出了如上信息

现在我们再次修改一下index.php的代码

<?php
function loaderA(){
	var_dump(1111);
    include './A.php';
}
function loaderB(){
   var_dump(2222);
   include './B.php';
}

spl_autoload_register('loaderA',true,true);
spl_autoload_register('loaderB',true,true);
B::test();
A::test();

访问index.php,会输出如下,提示我们重复引入了B.php文件,接下来我们好好分析一下为什么发生如下报错信息

int(2222)                                                                                                                                 
                                                                                                                                          
bint(2222)                                                                                                                                
                                                                                                                                          
PHP Fatal error:  Cannot declare class B, because the name is already in use in E:\phpstudy\phpstudy_pro\WWW\localhost\spl\B.php on line 4
                                                                                                                                          
Fatal error: Cannot declare class B, because the name is already in use in E:\phpstudy\phpstudy_pro\WWW\localhost\spl\B.php on line 4

在分析这个报错信息之前,我们需要先了解一个函数

spl_autoload_functions //返回所有已注册的 __autoload() 函数

看以下代码

<?php
function loaderA(){
	var_dump(1111);
    include './A.php';
}
function loaderB(){
   var_dump(2222);
   include './B.php';
}

spl_autoload_register('loaderA',true,true);
spl_autoload_register('loaderB',true,true);
$registers = spl_autoload_functions();
var_dump($registers);

执行以上代码输出以下信息,spl_autoload_register函数的最后一个参数=true,所以后注册的追加到队列最前面

array(2) {
  [0]=>
  string(7) "loaderB"
  [1]=>
  string(7) "loaderA"
}

有了以上了解,我们就可以好好的分析上面的报错信息了,我们在上面注册了两个spl_autoload_register函数,所以当代码执行到第13行的时候,找不到B文件,就会按照队列 array('loaderB','loaderA')先去加载loaderB函数,执行第7,8行代码,所以打印出了int(2222)下面echo出了b,代码继续往下走,执行到14行的时候,发现找不到A文件,就会按照队列 array('loaderB','loaderA')重新去加载loaderB函数,此时代码重新走到第7行,打印出了int(2222),但是再往下执行第8行代码的时候,发现B文件已经被引入了,重复引入就报了上面的错误。

我们修改一下index.php代码

<?php
function loaderA(){
    var_dump(1111);
    include './A.php';
}
function loaderB(){
    var_dump(2222);
    include './B.php';
}

spl_autoload_register('loaderA',true,true);
spl_autoload_register('loaderB',true,true);
A::test();
B::test();

访问index.php,输出如下信息

int(2222)                                      
                                               
int(1111)                                      
ab

我们来分析一下,代码执行到13行,按照队列array('loaderB','loaderA')会调用loaderB函数,执行第7行代码会输出int(2222)  ,执行第8行代码,将B.php引入进来,此时发现A文件还是没有找到,此时按照队列array('loaderB','loaderA'),会往下调用loaderA函数,执行第3行代码,输出int(1111)  ,继续执行第4行代码,此时会echo 出a,代码走到第14行,此时发现B.php已经被引入进来了,所以执行echo输出了b。

重新修改一下index.php代码,加了include_once,修改了A跟B的先后调用顺序

<?php
function loaderA(){
    var_dump(1111);
    include_once './A.php';
}
function loaderB(){
    var_dump(2222);
    include_once './B.php';
}

spl_autoload_register('loaderA',true,true);
spl_autoload_register('loaderB',true,true);
B::test();
A::test();

访问index.php,输入如下信息

int(2222)                                      
                                               
bint(2222)                                     
int(1111)                                      
a

我们来分析一下,代码执行到13行,首先访问loaderB函数,输出int(2222) ,echo出了b,代码执行到14行,发现A.php没加载进来,按照队列从头开始执行,首先访问loaderB函数,输出int(2222) ,因为使用了include_once,所以B.php不会被重复加载,继续按照队列往后走,访问loaderA函数,输出int(1111)   echo出了a     

其实loaderA 跟 loaderB函数是可以接受参数的,我们修改一下index.php文件

<?php
function loader($file){
    var_dump($file);
    include './'.$file.'.php';
}
spl_autoload_register('loader',true,true);
B::test();
A::test();

    访问index.php会输出如下

string(1) "B"                                  
                                               
bstring(1) "A"                                 
a

       此时队列中数据为array('loader');  $file参数为要加载的没有找到的类名。分析一下代码执行流程:代码执行到第7行,调用loader函数,此时file= 'B',加载了B.php,代码继续往下执行,走到第8行,发现A.php也没加载进来,此时又重新调用loader函数,此时file= 'A',加载了A.php

总结

spl_autoload_register 是按照注册队列顺序执行的,如果前面的加载没有找到文件,就顺序执行队列后面的,直到执行到最后一个,有则加载,无则报错。

我们平时在写代码的时候可能会用不到该函数,但是基本主流框架中都会使用到该函数,如Tp6,Laravel,Yii2等。因为框架的执行第一步基本都是使用spl_autoload_register函数处理文件加载,了解该函数有助于我们了解composer包的执行原理。如下,在Tp6中引入了composer包,在包中使用了该函数处理了文件加载。

spl_autoload_register的执行原理_Tp6

上一篇:ThinkPHP5默认的目录结构
下一篇:没有了
网友评论