关于php自动加载

134 阅读6分钟
自动加载可以说是现代
PHP框架的根基,任何牛逼的框架或者架构都会用到它,它发明出来的理由是啥呢?一个字:
。因为项目越来愈大,相关联的类库文件越来越多,我们不可能再像小项目那样在一个文件中全部手动一个一个
require

如何才能自动加载呢?
PHP 5.2版本更新了
自动加载
需要的一个魔术方法
——
__autoload($class_name)

正是这个神奇的内置魔术函数,才能让我们这些屌丝偷懒。我们来看下这个如何使用它。

1. 自动加载的原理以及__autoload的使用
自动加载的原理,就是在我们
new一个class
的时候,
PHP系统如果找不到你这个类,就会去自动调用本文件中的
__autoload($class_name)
方法,我们
new的这个class_name 就成为这个方法的参数。所以我们就可以在这个方法中根据我们需要new class_name的各种判断和划分就去
require
对应的路径类文件,从而实现自动加载。

我们先一步步来,看下
__autoload()的自动调用,看个例子:

index.php

[AppleScript]
纯文本查看
复制代码
1
$db = new DB();

如果我们不手动导入
DB类,程序可能会报错,说找不到这个类:

[AppleScript]
纯文本查看
复制代码
1
Fatal error: Class 'DB' not found in D:\wamp\www\testphp\autoload\index.php on line 3
那么,我们现在加入
__autoload()这个方法再看看:

[AppleScript]
纯文本查看
复制代码
1
2
3
4
5
6
$db = new DB();
function __autoload($className)
{
echo $className;
exit();
}

根据上面自动加载机制的描述,你分析下会输出什么?
没错:肯定是输出:
DB
也就是我们需要
new 的类的类名。所以,这个时候我们就可以在__autoload()方法里,根据需要去加载类库文件了。

index.php

[AppleScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
$db = new DB();
function __autoload($className)
{
require $className . '.php';
}
DB.php
class DB
{
public function __construct()
{
echo 'Hello DB';
}
}

这样子我们就很轻松的将我们需要
new 的class 全部导入了进来,这样子,我们就可以轻松的new N个class,比如:

[AppleScript]
纯文本查看
复制代码
1
2
3
4
5
6
7
8
9
<?php
function __autoload($className)
{
require $className . '.php';
}
$db = new DB();
$info = new Info();
$gender = new Gender();
$name = new Name();

//也是支持静态方法直接调用的

[AppleScript]
纯文本查看
复制代码
1
Height::test();
2. spl_autoload_register的使用
小的项目,用
__autoload()
就能实现基本的自动加载了。但是如果一个项目过大,或者需要不同的自动加载来加载不同路径的文件,这个时候
__autoload
就悲剧了,原因是一个项目中仅能有一个这样的
__autoload()
函数,因为 PHP 不允许函数重名,也就是说你不能声明2个
__autoload()
函数文件,否则会报致命错误,我了个大擦,那怎么办呢?放心,你想到的,
PHP开发大神早已经想到。

所以
spl_autoload_register()
这样又一个牛逼函数诞生了,并且取而代之它。
它执行效率更高,更灵活

先看下它如何使用吧:

当我们去
new一个找不到的class
时,
PHP就会去自动调用sql_autoload_resister注册的函数,这个函数通过它的
参数
传进去:

sql_autoload_resister($param)
这个参数可以有多种形式:

sql_autoload_resister('load_function'); //函数名

sql_autoload_resister(array('load_object', 'load_function')); //类和静态方法

sql_autoload_resister('load_object::load_function'); //类和方法的静态调用

[AppleScript]
纯文本查看
复制代码
1
2
3
4
5
6
//php 5.3之后,也可以像这样支持匿名函数了。
spl_autoload_register(function($className){
if (is_file('./lib/' . $className . '.php')) {
require './lib/' . $className . '.php';
}
});

index.php

[AppleScript]
纯文本查看
复制代码
1
2
3
4
5
6
7
function load1($className)
{
echo 1;
require $className . '.php';
}
spl_autoload_register('load1'); //将load1函数注册到自动加载队列中。
$db = new DB(); //找不到DB类,就会自动去调用刚注册的load1函数了

上面就是实现了自动加载的方式,我们同样也可以用
类加载
的方式调用,但是必须是
static方法

[AppleScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
class autoloading{
//必须是静态方法,不然报错
public static function load($className)
{
require $className . '.php';
}
}
//2种方法都可以
spl_autoload_register(array('autoloading','load'));
spl_autoload_register('autoloading::load');
$db = new DB(); //会自动找到

需要注意的是,如果你同时使用
spl_autoload_register和__autoload,__autoload会失效!!!
再说了,本来就是替换它的,就一心使用spl_autoload_register就好了。

3. 多个spl_autoload_register的使用
spl_autoload_register是可以多次重复使用的,这一点正是解决了__autoload的短板,那么如果一个页面有多个,
执行顺序是按照注册的顺序,一个一个往下找,如果找到了就停止。

我们来看下这个例子,
DB.php就在本目录下,Info.php在/lib/目录下。

[AppleScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function load1($className)
{
echo 1;
if (is_file($className . '.php')) {
require $className . '.php';
}
}
function load2($className)
{
echo 2;
if (is_file('./app/' . $className . '.php')) {
require './app/' . $className . '.php';
}
}
function __autoload($className)
{
echo 3;
if (is_file('./lib/' . $className . '.php')) {
require './lib/' . $className . '.php';
}
}
//注册了3
spl_autoload_register('load1');
spl_autoload_register('load2');
spl_autoload_register('__autoload');
$db = new DB(); //DB就在本目录下
$info = new Info(); //Info 在/lib/Info.php

我们注册了
3个自动加载函数。执行结果是啥呢?

[AppleScript]
纯文本查看
复制代码
1
2
1Hello DB
123Hello Info

我们分析下:

1.
new DB的时候,就按照注册顺序,先去找load1()函数了,发现找到了,就停止了,所以输出1 Hello Word

2.
new Info的时候,先是安装注册顺序,先找load1(), 所以输出了1,发现没找到,就去load2()里面去找,所以输出了2,还是没这个文件,就去__autoload()函数里找,所以,先输出了3,再输出Hello Info

注意,前面说过,
spl_autoload_register使用时,__autoload会无效,有时候,我们希望它继续有效,就可以也将它注册进来,就可以继续使用。

我们可以打印
spl_autoload_functions()
函数,来显示一共注册了多少个自动加载:

[AppleScript]
纯文本查看
复制代码
1
2
3
4
5
6
var_dump(spl_autoload_functions());
//数组的形式输出
array (size=3)
0 => string 'load1' (length=5)
1 => string 'load2' (length=5)
2 => string '__autoload' (length=10)

4. spl_autoload_register自动加载+namespace命名空间 的使用
前面已经说过,自动加载现在是
PHP现代框架的基石,基本都是spl_autoload_register来实现自动加载。namespace也是使用比较多的。所以spl_autoload_register + namespace 就成为了一个主流。根据PSR-0的规范,namespace命名已经非常规范化,所以用namespace就能找到详细的路径,从而找到类文件。

我们举例子来看下:

[AppleScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
AutoLoading\loading
<?php
namespace AutoLoading;
class loading {
public static function autoload($className)
{
//根据PSR-O的第4点 把 \ 转换层(目录风格符) DIRECTORY_SEPARATOR ,
//便于兼容Linux文件找。Windows 下(/ 和 \)是通用的
//由于namspace 很规格,所以直接很快就能找到
$fileName = str_replace('\\', DIRECTORY_SEPARATOR, DIR . '\\'. $className) . '.php';
if (is_file($fileName)) {
require $fileName;
} else {
echo $fileName . ' is not exist'; die;
}
}
}

上面就是一个自动加载的核心思想方法。下面我们就来
spl_autoload_register
来注册这个函数:

index.php

[AppleScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<?php
//定义当前的目录绝对路径
define('DIR', dirname(__FILE__));
//加载这个文件
require DIR . '/loading.php';
//采用`命名空间`的方式注册。php 5.3 加入的
//也必须是得是static静态方法调用,然后就像加载namespace的方式调用,注意:不能使用use
spl_autoload_register("\\AutoLoading\\loading::autoload");
// 调用三个namespace类
//定位到Lib目录下的Name.php
Lib\Name::test();
//定位到App目录下Android目录下的Name.php
App\Android\Name::test();
//定位到App目录下Ios目录下的Name.php
App\Ios\Name::test();

由于我们是采用
PSR-O
方式来定义
namespace的命名的,所以很好的定位到这个文件的在哪个目录下了。很爽。对不对。

[AppleScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
APP\Android\Name
namespace App\Android;
class Name
{
public function __construct()
{
echo __NAMESPACE__ . "<br>";
}
public static function test()
{
echo __NAMESPACE__ . ' static function test <br>';
}
}

所以就会很容易找到文件,并输出:

[AppleScript]
纯文本查看
复制代码
1
2
3
Lib static function test
App\Android static function test
App\Ios static function test

好了。基本自动加载的东西就讲完了。很实用的东西。

4. 同命名空间下的相互调用
在平时我们使用命令空间时,有时候可能是在同一个命名空间下的
2个类文件在相互调用。这个时候就要注意,在自动调用的问题了。

比如
Lib\Factory.php 和 Lib\Db\MySQL.php

我想在
Lib\Factory.php 中调用 Lib\Db\MySQL.php。怎么调用呢?以下是错误的示范:


[AppleScript]
纯文本查看
复制代码
1
2
new Lib\Db\MySQL();
//报错,提示说 D:\wamp\www\testphp\module\Lib\Lib\Db\MySQL.php is not exist

看到没?这种方式是在
Lib\命名空间的基础上来加载的。所以会加载2个Lib。这种方式相当于相对路径在加载。

正确的做法是,如果是在同一个命名空间下平级的
2个文件。
可以直接调用,不用命名空间。

[AppleScript]
纯文本查看
复制代码
1
2
new MySQL(); //直接这样就可以了。
new Db\MySQL(); //如果有个Db文件夹,就这样。

还有一种方法就是使用
use
。使用user就可以带上Lib了。
use
使用的是绝对路径。

[AppleScript]
纯文本查看
复制代码
1
2
use Lib\Db\MySQL;
new MySQL();

我想在
Lib\Db\MySQL.php 中调用 Lib\Register.php。怎么调用呢?

应该这样

[AppleScript]
纯文本查看
复制代码
1
2
use Lib\Register;
Register::getInstance();

因为现在已经在
Lib\Db这样一个命名空间了,如果你不用
use
,而是使用
Lib\Register::getInstance()
或者使用
Register::getInstance()
的话。将是在
Lib\Db这个空间下进行相对路径的加载,是
错误
的。