【背景】
在使用OO(object oriented,面向对象)模式开发时,我们通常把一个class单独放在一个文件里,并以类名作为php文件名,这样方便管理维护。
在PHP5之前,使用一个类前总是需要将它所在的文件include/require进来,如果使用该类的地方较多或某个文件引用的类较多时,就得不停地手动include/require,这样不仅麻烦,而且容易遗漏。这便带来一个问题——当项目越来越大,如何方便快速地加载我们想要的类文件?
于是,PHP5开始有了自动加载机制。
【原理】
从PHP5开始,PHP在使用一个类时,如果这个类没有被加载,会 autoload_func,__autoload() 等2种方式实现自动加载。autoload_func是在拓展插件(即PHP默认内置或ext目录下的拓展)中实现的自动加载方法——全局可用,__autoload()是PHPer自己在PHP代码中定义的自动加载方法——有引入__autoload所在的文件才可用,故一般放在公共文件中。
当PHP发现使用的类没有被加载时,执行的优先级如下:
①查看当前拓展插件中是否存在autoload_func方法,若有,则使用autoload_func中定义的规则实现自动加载;若无,则进入下一步骤;
②查看当前PHP代码中是否存在__autoload()方法,若有,则使用__autoload()中定义的规则实现自动加载;若无,则抛出异常——报错提示“class 'XXX' not found”。
即,若插件中已实现autoload_func方法,则不会执行PHPer定义的__autoload()方法。因此,自动加载只能选择其中一种方式实现。
目前PHP已内置提供实现autoload_func的拓展插件——SPL(此扩展从PHP 5.3.0 不再被关闭,会一直有效.成为php内核组件一部份),其中的SPL函数便是专门处理自动加载相关的操作。
【实现】
⑴先看__autoload()方法实现自动加载的方式
这是index.php文件
<?php function __autoload($class) { $file = $class.'.php'; if(file_exists($file)){ include $file; } } $obj = new Test();
这是Test.php文件
<?php class Test { function __construct() { echo 'hello world'; } }
以上输出:
hello world
⑵再看看拓展插件实现自动加载
目前只有PHP内置的SPL拓展实现autoload_func,其他拓展暂未发现——毕竟官方内置拓展已实现,无需再实现。这里便用SPL拓展为例。
①方式一(注册默认加载规则,即spl_autoload())
这是index.php文件
<?php spl_autoload_register(); $obj = new Test();
这是Test.PHP文件
<?php class Test { function __construct() { echo 'hello world'; } }
②方式二(注册自定义加载规则)
这是index.php文件
<?php function __autoload($class) { $file = $class.'.php'; if(file_exists($file)){ include $file; } } function register($class) { $file = 'extend/'.$class.'.php'; if(file_exists($file)){ include $file; } } class Reader { static function load($class) { $file = 'app/controller/'.$class.'.php'; if(file_exists($file)){ include $file; } } } spl_autoload_register('__autoload');//原代码中,PHPer已实现__autoload(),但在使用SPL拓展之后,__autoload()便失效,须手动添加到注册表中才会生效 spl_autoload_register('register');//把定义了规则的方法添加到注册表中 spl_autoload_register(['Reader','load']);//把定义了规则的类中的函数添加到注册表中 $objTest = new Test(); $objSms = new Sms(); $objUser = new User();
这是Test.PHP文件
<?php class Test { function __construct() { echo 'hello world'.PHP_EOL; } }
这是extend/Sms.php
<?php class Sms { function __construct() { echo 'I am Sms'.PHP_EOL; } }
这是app/controller/User.php
<?php class User { function __construct() { echo 'I am User'.PHP_EOL; } }
以上输出:
hello world I am Sms I am User
【注意】
1.不使用SPL系列的函数时,PHPer定义的__autoload()才会生效。
2.若原代码中,PHPer已实现__autoload(),但在使用SPL拓展(即,使用了SPL系列的函数)之后,__autoload()将失效,须手动添加到注册表中才会生效。
3.注册到SPL中的规则按照注册顺序进行匹配,但当有一个规则引入当前要加载的类之后,后面的规则便不再匹配运行。(例如,上述的extend/下也存在一个User类时,因为'register'的注册顺序在['Reader', 'load']之前,故只会加载extend下的User类,不会加载app/controller/下的User类)
4.__autoload()在多人开发时不便于维护,推荐使用SPL,只需使用spl_autoload_register()注册各自所要的规则即可,互不干扰。
【命名空间】
当类越来越多时,很容易造成类名冲突,__autoload()无法识别相同类名所在文件,而SPL的注册表按顺序匹配规则,永远只会加载首个匹配到的类,排后面的同名的类都加载不进来了。这时便需要使用命名空间来管理。
有了命名空间之后,一个类的逻辑位置由PHPer自己声明,而该类所在文件的物理位置在__autoload()或SPL的注册表定义好规则引入,相当于做映射绑定。
⑴先看__autoload()方法如何加载命名空间的类
这是index.php文件
<?php function __autoload($class) { $arr = explorer('\\', $class); if($arr[1]=='app'){ $arr[1] = 'app/controller'; } if($arr[1]=='extend'){ $arr[1] = 'extend'; } $class = implode('/', $arr);//将\替换成/,兼容Windows和Linux $file = $class.'.php'; if(file_exists($file)){ include $file; } } $objA = new \app\User(); $objB = new \extend\User();
这是app/controller/User.php
<?php namespace app; class User { function __construct() { echo 'I am app's User'.PHP_EOL; } }
这是extend/User.php
<?php namespace extend; class User { function __construct() { echo 'I am extend's User'.PHP_EOL; } }
⑵再看SPL的spl_autoload_register如何加载命名空间的类
这是index.php文件
class Reader { static function app($class) { $arr = explorer('\\', $class); if($arr[1]=='app'){ $arr[1] = 'app/controller'; } $file = implode('/', $arr) . '.php';//将\替换成/,兼容Windows和Linux self::includeFile($file); } static function extend($class) { $arr = explorer('\\', $class); if($arr[1]=='extend'){ $arr[1] = 'extend'; } $file = implode('/', $arr) . '.php';//将\替换成/,兼容Windows和Linux self::includeFile($file); } static function includeFile($file) { if(file_exists($file)){ include $file; } } } spl_autoload_register(['Reader','app']); spl_autoload_register(['Reader','extend']); $objA = new \app\User(); $objB = new \extend\User();
这是app/controller/User.php
<?php namespace app; class User { function __construct() { echo 'I am app's User'.PHP_EOL; } }
这是extend/User.php
<?php namespace extend; class User { function __construct() { echo 'I am extend's User'.PHP_EOL; } }
【问答】
1.为什么要用spl_autoload_register来取代__autoload()?
答:因为__autoload()只是一个加载一次的方法,且全部的加载规则放在一个方法中维护不是最优方案。 相比之下,SPL拓展提供了很多操作加载规则的函数,非常灵活。 而spl_autoload_register()可以多处注册规则,且各个模块的规则可以分开维护。
2.spl_autoload()函数的作用以及其和spl_autoload_register()的关系?
答:spl_autoload()是SPL实现的默认加载规则——应是默认类的命名空间与文件的物理位置相同。 当spl_autoload_register()不带参数时,便自动调用此函数。 据说spl_autoload()是历史遗留问题,虽然PHPer可以调用,但参数有限,且现在也没有什么作用。 个人感觉可能就是解决SPL注册表中没有规则时的问题。
参考文档:
以上只是个人的理解总结,欢迎交流。