php类文件的自动加载机制

通常情况,在使用php面向对象开发的过程中,一个类的定义都是一个文件,这样子下来,当类与类之间需要相互引用的时候就需要include(require)相应的类文件,如此一来带来的一个不是问题的问题,就是每次需要用到某个类的时候就需要去手工include(require)。

在php5之后已经有了类的自动加载机制,可以定义__autoload函数,在使用到某个未定义的类,执行php会出错,但是在此时php引擎在返回失败之前会去check下是否有定义__autoload去加载需要的类。

目前可以是用php的__autoload机制或者SPL(Standard PHP Library)的spl_autoload机制来自动加载。

0. php中自动加载机制的主要原理

通过查阅php的源代码,在zend api中的可以发现php在初始化执行器时:
1. 会先将autoload的注册函数清空;
2. 然后执行过程中如果要获取某个类时通过zend_fetch_class函数获取;
3. 而zend_fetch_class中通过zend_lookup_class_ex函数去查找对应的类是否存在,在zend_lookup_class_ex函数中实现了自动加载的功能。
如以下代码:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 初始化执行器
void init_executor(TSRMLS_D) /* {{{ */
{
     ....
     EG(in_autoload) = NULL;
     EG(autoload_func) = NULL;
     ....
}
// 获取类指针
zend_class_entry *zend_fetch_class( const char *class_name, uint class_name_len, int fetch_type TSRMLS_DC) /* {{{ */
{
     zend_class_entry **pce;
     int use_autoload = (fetch_type & ZEND_FETCH_CLASS_NO_AUTOLOAD) == 0;
     ...
     if (zend_lookup_class_ex(class_name, class_name_len, NULL, use_autoload, &pce TSRMLS_CC) == FAILURE) {
         ...
     }
     return *pce;
}
// 查找类是否存在
ZEND_API int zend_lookup_class_ex( const char *name, int name_length, const zend_literal *key, int use_autoload, zend_class_entry ***ce TSRMLS_DC) /* {{{ */
{
     zval **args[1];
     zval autoload_function;
     ...
     /* The compiler is not-reentrant. Make sure we __autoload() only during run-time
     * (doesn't impact fuctionality of __autoload()
     */
     if (!use_autoload || zend_is_compiling(TSRMLS_C)) {
         ...
         return FAILURE;
     }
     ...
     fcall_info.size = sizeof (fcall_info);
     fcall_info.function_table = EG(function_table);
     fcall_info.function_name = &autoload_function;      // 这里的autoload_function指定义的__autoload函数
     ...
     fcall_cache.initialized = EG(autoload_func) ? 1 : 0;
     fcall_cache.function_handler = EG(autoload_func);   // 这里的autoload_func指注册autoload_func函数
     ...
     retval = zend_call_function(&fcall_info, &fcall_cache TSRMLS_CC);
     ...
     return retval;
}

也就可以发现php在自动加载类时最重要的异步是:zend_call_function(&fcall_info, &fcall_cache TSRMLS_CC);
当中fcall_info指针里面的function_name是指php自带的“__autoload”函数
而fcall_cache指针里面定义的function_handler是用户注册的autoload_func
而在zend_call_function中执行过程有一个重要步骤是:
查看fcall_cache是否定义:如果有定义,则执行fcall_cache的handler;如果没有定义,则才执行fcall_info.function_name,也就是__autoload函数。

所以,综上可以结论:
php的自动加载未定义类过程:
1. 检查是否注册autoload_func
2. 如果没有注册autoload_func,则检查是否定义了__autoload;如果有定义__autoload则执行用户定义的加载方式,如果没有定义__autoload则返回错误。
3. 如果有注册autoload_func则执行注册的加载函数,不执行__autoload。

php有两种自动加载方式:
1. 通过定义__autoload函数来处理未定义类的加载;
2. 通过注册autoload_func来处理未定义类的加载,即:通过spl_autoload_register来注册autoload_func
当中注册autoload_func的方式的优先级高于使用__autoload方式,也就是说,假如同时定义__autoload和注册autoload_func,只会执行注册的autoload_func,不会继续执行__autoload。

1.使用__autoload方式
void __autoload ( string $class )
如:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* Foo.class.php */
class Foo{
     function __construct()
     {
         echo 'constructed' ;
     }
}
/* test_autoload.php*/
function __autoload( $className )
{
     include ( $className . '.class.php' );
}
$foo = new Foo(); // output:constructed

在test.php中就不需要加载Foo.class.php了,__autoload会自动加载类Foo的定义。
不过要注意的是:
1. 在php 5.3.0 版之前,__autoload 函数抛出的异常不能被 catch 语句块捕获并会导致一个致命错误。从php 5.3.0+ 之后,__autoload 函数抛出的异常可以被 catch 语句块捕获,但需要遵循一个条件。如果抛出的是一个自定义异常,那么必须存在相应的自定义异常类。
2. 一个项目中只能定义一个__autoload,假如说当项目中引用了第三方文件时,刚好第三方文件中也使用了__autoload;或者项目成员多的时候,定义了不同的__autoload;此时就会出错,因为__autoload函数拥有全局域,php也支不支持函数重载,只能将几个__autoload合并成一个。
如此一来就有了更加灵活的spl_autoload_register()的自动加载机制,php官方也建议不要使用__autoload而使用spl_autoload_register()。

2. 使用spl_autoload_register方式
SPL(Standard PHP Library),是php的标准库,php5.1+ 支持。spl的自动加载机制主要原理就是通过spl_autoload_register注册函数,并且将php中autoload_func指向了注册的函数。如果通过spl_autoload_register来注册函数,spl会执行它默认的spl_autoload()函数。
bool spl_autoload_register ([ callback $autoload_function ] )

1
2
3
4
5
6
7
/* test_autoload.php*/
function autoloadHandler( $className )
{
     include ( $className . '.class.php' );
}
spl_autoload_register( 'autoloadHandler' );
$foo = new Foo(); // output:constructed

如果是在类中定义的handler函数,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* test_autoload.php*/
class Autoloader
{
     static function init()
     {
         spl_autoload_register( $this , 'autoloadHandler' );
     }
     public function autoloadHandler( $className )
     {
         include ( $className . '.class.php' );
     }
}
Autoloader::init();
$foo = new Foo(); // output:constructed

上面就是php类自动加载的主要两种方式,我觉得使用自动加载的好处有:

1. 不在需要需要用到什么类文件就require(include)了
2. 在设置autoload的过程添加一个map缓存已经加载过的类,也不再需要使用require_once(include_once)了
3. 按需加载,避免require(include)不必要的文件


补充:

在PHP开发过程中,如果希望从外部引入一个class,通常会使用include和require方法,去把定义这个class的文件包含进来,但是这样可能会使得在引用文件的新脚本中,存在大量的include或require方法调用,如果一时疏忽遗漏则会产生错误,使得代码难以维护。

自PHP5后,引入了__autoload这个拦截器方法,可以自动对class文件进行包含引用,通常我们会这么写: 

复制代码代码如下:

function __autoload($className) { 
include_once $className . '.class.php'; 


$user = new User(); 

当PHP引擎试图实例化一个未知类的操作时,会调用__autoload()方法,在PHP出错失败前有了最后一个机会加载所需的类。因此,上面的这段代码执行时,PHP引擎实际上替我们自动执行了一次__autoload方法,将User.class.php这个文件包含进来。 

在__autoload函数中抛出的异常不能被catch语句块捕获并导致致命错误。 

如果使用 PHP的CLI交互模式时,自动加载机制将不会执行。 

当你希望使用PEAR风格的命名规则,例如需要引入User/Register.php文件,也可以这么实现: 
复制代码代码如下:

//加载我 
function __autoload($className) { 
$file = str_replace('_', DIRECTORY_SEPARATOR, $className); 
include_once $file . 'php'; 

$userRegister = new User_Register(); 


这种方法虽然方便,但是在一个大型应用中如果引入多个类库的时候,可能会因为不同类库的autoload机制而产生一些莫名其妙的问题。在PHP5引入SPL标准库后,我们又多了一种新的解决方案,spl_autoload_register()函数。 

此函数的功能就是把函数注册至SPL的__autoload函数栈中,并移除系统默认的__autoload()函数。一旦调用spl_autoload_register()函数,当调用未定义类时,系统会按顺序调用注册到spl_autoload_register()函数的所有函数,而不是自动调用__autoload()函数,下例调用的是User/Register.php而不是User_Register.class.php: 
复制代码代码如下:

//不加载我 
function __autoload($className) { 
include_once $className . '.class.php'; 

//加载我 
function autoload($className) { 
$file = str_replace('/', DIRECTORY_SEPARATOR, $className); 
include_once $file . '.php'; 

//开始加载 
spl_autoload_register('autoload'); 
$userRegister = new User_Register(); 


在使用spl_autoload_register()的时候,我们还可以考虑采用一种更安全的初始化调用方法,参考如下: 
复制代码代码如下:

//系统默认__autoload函数 
function __autoload($className) { 
include_once $className . '.class.php'; 

//可供SPL加载的__autoload函数 
function autoload($className) { 
$file = str_replace('_', DIRECTORY_SEPARATOR, $className); 
include_once $file . '.php'; 

//不小心加载错了函数名,同时又把默认__autoload机制给取消了……囧 
spl_autoload_register('_autoload', false); 
//容错机制 
if(false === spl_autoload_functions()) { 
if(function_exists('__autoload')) { 
spl_autoload_register('__autoload', false); 



奇技淫巧:在Unix/Linux环境下,如果你有多个规模较小的类,为了管理方便,都写在一个php文件中的时候,可以通过以ln -s命令做软链接的方式快速分发成多个不同类名的拷贝,再通过autoload机制进行加载。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值