PHP 类的自动加载

以前看过这块的概念与讲解,知道通过composer构建的现代化框架都是通过spl_autoload_register函数进行类的自动加载,现在形成笔记记录一下

类的加载方式

  • 手动引入
  • __autoload
  • spl_autoload_register

手动引入

就相当了刚开始学习php时的几个函数 include、include_once、require、require_once 。

寻找方式:

  1. 按参数给出的路径寻找
  2. 没有目录(只有文件名)按照include_path指定的目录去寻找
  3. 调用脚本文件所在目录和当前工作目录下寻找

include

实验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

// 正常引入
$includeRes = include 'AutoloadDemo.php';
var_dump($includeRes);
AutoloadDemo::hello();

// 错误引入
$includeRes = include 'AutoloadDemo1.php';
var_dump($includeRes);

// 重复引入
$includeRes = include 'AutoloadDemo.php';
var_dump($includeRes);
AutoloadDemo::hello();

结果:

1
2
3
4
5
6
7
8
9
10
11
// 正常引入
int(1)
Hello AutoloadDemo

// 错误引入
PHP Warning: include(AutoloadDemo1.php): failed to open stream: No such file or directory
bool(false)

// 重复引入
PHP Fatal error: Cannot declare class AutoloadDemo, because the name is already

  • include成功时,返回值为int(1),失败的返回值为bool(false)
  • 错误引入是warning,重复引入是error

include_once

实验:

1
2
3
4
5
6
7
8
9
10
11
12
<?php

// 正常引入
$includeRes = include_once 'AutoloadDemo.php';
var_dump($includeRes);
AutoloadDemo::hello();

// 重复引入
$includeRes = include_once 'AutoloadDemo.php';
var_dump($includeRes);
AutoloadDemo::hello();

1
2
3
4
5
6
7
8
9
10
11
12
<?php

// 正常引入
$includeRes = include 'AutoloadDemo.php';
var_dump($includeRes);
AutoloadDemo::hello();

// 重复引入
$includeRes = include_once 'AutoloadDemo.php';
var_dump($includeRes);
AutoloadDemo::hello();

结果:

1
2
3
4
int(1)
Hello AutoloadDemo
bool(true)
Hello AutoloadDemo
1
2
3
4
int(1)
Hello AutoloadDemo
bool(true)
Hello AutoloadDemo
  • 第一次引入成功时,都是返回的int(1)
  • 重复引入时,会直接返回bool(true)

require

实验:

1
2
3
4
5
6
7
8
9
10
11
<?php

// 正常引入
$includeRes = require 'AutoloadDemo.php';
var_dump($includeRes);
AutoloadDemo::hello();

// 错误引入
$includeRes = require 'AutoloadDemo1.php';
var_dump($includeRes);

1
2
3
4
5
6
7
8
9
10
11
<?php

// 正常引入
$includeRes = require 'AutoloadDemo.php';
var_dump($includeRes);
AutoloadDemo::hello();

// 重复引入
$includeRes = require 'AutoloadDemo.php';
var_dump($includeRes);
AutoloadDemo::hello();

结果:

1
2
3
4
5
int(1)
Hello AutoloadDemo
PHP Warning: require(AutoloadDemo1.php): failed to open stream: No such file or directory
Fatal error: require(): Failed opening required 'AutoloadDemo1.php

1
2
3
int(1)
Hello AutoloadDemo
PHP Fatal error: Cannot declare class AutoloadDemo, because the name is already
  • 成功引入时跟include一样,返回值都是int(1)
  • 错误引入与重复引入,都会抛出error

require_once

实验:

1
2
3
4
5
6
7
8
9
10
11
<?php

// 正常引入
$includeRes = require 'AutoloadDemo.php';
var_dump($includeRes);
AutoloadDemo::hello();

// 重复引入
$includeRes = require_once 'AutoloadDemo.php';
var_dump($includeRes);
AutoloadDemo::hello();
1
2
3
4
5
6
7
8
9
10
11
<?php

// 正常引入
$includeRes = require_once 'AutoloadDemo.php';
var_dump($includeRes);
AutoloadDemo::hello();

// 重复引入
$includeRes = require_once 'AutoloadDemo.php';
var_dump($includeRes);
AutoloadDemo::hello();

结果:

1
2
3
4
int(1)
Hello AutoloadDemo
bool(true)
Hello AutoloadDemo
1
2
3
4
int(1)
Hello AutoloadDemo
bool(true)
Hello AutoloadDemo
  • 跟include一样

区别

  • require一个文件存在错误的话,那么程序就会中断执行了,并显示致命错误。
  • include一个文件存在错误的话,那么程序不会中端,而是继续执行,并显示一个警告错误。

现在越来越趋向于严格的编码,所以require错误直接中断执行,才是可靠的

__autoload

__autoload() 在 PHP 7.2.0 起弃用,在 PHP 8.0.0 起移除。

简单示例一下就行了,毕竟现在已经没人用了吧

1
2
3
4
5
6
7
8
9
10
11
<?php

function __autoload(string $classname)
{
$file = $classname . '.php';
if (file_exists($file)) {
require_once $file;
}
}

AutoloadDemo::hello();
  • 遇到未包含的类,会触发 __autoload 进行加载,如果所有加载规则中没有此类,则 Fatal error。
  • 不支持多个自动加载的函数。

spl_autoload_register

spl_autoload_register() 函数可以注册任意数量的自动加载器,当使用尚未被定义的类(class)和接口(interface)时自动去加载。通过注册自动加载器,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。
将函数注册到SPL autoload函数队列中。如果该队列中的函数尚未激活,则激活它们。如果在你的程序中已经实现了autoload()函数,它必须显式注册到__autoload()队列中。

实验:

1
2
3
4
5
6
7
.
├── demo1
│   └── AutoloadDemo1.php
├── demo2
│   └── AutoloadDemo2.php
└── spl_autoload_register.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

$autoload1 = function (string $classname) {
$path = './demo1/' .$classname . '.php';
echo "尝试使用autoload1 加载类 {$classname}" .PHP_EOL;
if (file_exists($path)) {
require_once $path;
}
};

$autoload2 = function (string $classname) {
$path = './demo2/' . $classname . '.php';
echo "尝试使用autoload2 加载类 {$classname}" .PHP_EOL;
if (file_exists($path)) {
require_once $path;
}
};

spl_autoload_register($autoload1);
spl_autoload_register($autoload2);

AutoloadDemo1::hello();
AutoloadDemo2::hello();

结果:

1
2
3
4
5
6
尝试使用autoload1 加载类 AutoloadDemo1
Hello AutoloadDemo1

尝试使用autoload1 加载类 AutoloadDemo2
尝试使用autoload2 加载类 AutoloadDemo2
Hello AutoloadDemo2
  • spl_autoload_register会维护一个autoload队列,可添加多个
  • 当调用未知类时,触发autoload队列
  • 依次调用注册的autoload队列,直到队列结束 或者 找到类
  • 当使用 spl_autoload_register() 后当 new 一个未包含的类时候,会去执行 spl_autoload_register() 第一个参数函数名的函数,这个函数有一个参数就是需要 new 的类名,这个函数的功能就是把这个类给包含进来(类名和文件名一致),这样就实现了自动加载功能