应用场景
在PHP中,当我们想要使用其它文件时,需要先引入文件,这时候我们会使用include或者require引入。当我们的项目只有几个文件时,使用这种方式手动引入,就很nice,但是现在的项目都是使用框架,动辄几百上千个文件,如果还是使用这种方法引入的话,就会出现以下问题:
- 文件刚打开,发现头部好多include,很不美观
- 有时候不确定某个文件是否引入了,还得先调试代码,发现报错了,再手动引入,这种开发模式就很不友好
所以有没有解决这种问题的方法,可以自动引入呢?答案当然是有了,PHP在SPL标准库中给我们内置了今天的主角,也就是spl_autoload_register函数。
怎么用
定义:将给定函数注册为 __autoload() 实现,即自动加载函数
function spl_autoload_register(?callable $callback, bool $throw = true, bool $prepend = false): bool {}该函数接受三个参数,
- 闭包函数的名字,也可以是类的方法,也可以是闭包
- 是否应在 无法注册 时抛出异常 默认为true,从 PHP 8.0.0 开始,该参数被忽略,spl_autoload_register() 现在总是会在无效参数上 抛出TypeError 。
- 是否将注册函数放到队列的头部,默认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包,在包中使用了该函数处理了文件加载。

