php 自动加载

来源:互联网 发布:java 多线程 回调函数 编辑:程序博客网 时间:2024/06/03 13:09

1、手动加载方式
像C和C++等语言,在PHP中需要使用另一个文件中的相关的类、方法时,可以使用include、include_once、require或者require_once将所用的文件包含进工程里面。
其中,四者的区别如下。
include将引用一个文件,如果文件不存在,则给出一个提示,跳过继续执行;
include_once也是套用一个文件,但是只会套用一次,如果文件不存在,则继续执行;
require表示引用一个文件,如果文件不存在,则中断程序的执行;
require_once也是套用一个文件,且只会套用一次,如果文件不存在,则中断程序的执行;
以上四种方式是需要什么文件的时候,手动在程序当中包含进文件。这在项目的规模比较小的时候,是可以的;但是随着项目规模的扩大,要通过手动的方式加载每个文件所需要的类简直是一场噩梦。


为了省事,在加载的时候可以通过set_include_path()设置加载的路径,同样也可以通过get_include_path()获取加载的路径。关set_include_path()get_include_path(),这里只对set_include_path()作简要的介绍。首先,set_include_path()是在脚本中动态的对php.ini中的include_path进行动态的修改,而这个include_path就是include和require的路径进行设置,或者说是预定义。
假如,我们在一个main.php文件中需要使用projname/lvfk/webapi/test文件夹下的a.php、b.php、c.php......,如果没有设置包含的路径的话,那么写成如下的形式:

<?phpinclude("projname/lvfk/webapi/test/a.php");include("projname/lvfk/webapi/test/b.php");include("projname/lvfk/webapi/test/c.php");

以上的包含,都是使用的绝对路径,对与大型项目中不管是书写还是维护,都是很麻烦。而使用了set_include_path设置以后,显然整体代码清爽了许多

<?phpset_include_path("projname/lvfk/webapi/test");include("a.php");include("b.php");include("c.php");

第二种明显省去了很多的时间,但是仍然是要将每个文件包含进来,只是简化了包含的路径而已


2、自动加载方式
为了将双手从类的加载方式中解放出来,在PHP5及以后的版本中提供了一个自动加载的机制---autoload。Autoload可以使类在确实被需要的情况下才会被加载进来,也就是所谓的lazy loading,而不是一开始就include或者require所有的类文件。
其中PHP提供的自动加载机制又分为两种---__autoload()以及spl_autoload_register()

2.1、__autoload()机制

保证自动加载机制的的原则就是要使得类名和文件名具有一种对应关系,类名+后缀构成了这个类所在的文件的名字。

如果这个文件确实存在,那么就根据$fileName将该类加载进来。如果文件不存在,则提示用户,文件不存在。总的来说自动加载机制包括三个步骤:


1):根据类名确定文件名,也就是确定一种类名和文件名之间的统一对应规则;
2):根据文件名在磁盘上找到相应的对应文件;
3):将磁盘文件加载到文件系统中,这一步只是用一般的include和require包含相应的类文件;

  __autoload()实现类的自动加载的原则就是:类名和文件名之间具有一种统一的对应关系,这是在一个系统中实现__autoload的关键所在。
但是一个系统可能是有不同的人员所开发,如果在开发之前没有约定统一的标准,则可能存在不同的对应规则,导致需要在__autoload()中实现多种加载规则,
那么可能导致__autoload()函数非常的臃肿。为了解决这个这个问题,PHP还提供了一个自动加载机制---spl_autoload_register()
<?phpclass A{public function __construct(){echo 'Hello A';}}
A定义在A.php中
<?php$db = new A();function __autoload($classname){$fileName = $classname . ".php";if (file_exists($fileName)) {require_once("$fileName");} else {echo $fileName . " doesn't exist!";}}

以上运行后的结果为:Hello A


2.2、spl_autoload_register

SPL是Standard PHP Library(标准PHP库)的缩写,是PHP5引入的一个扩展库。SPL autoload是通过将函数指针autoload_func指向自动装载函数实现的。SPL具有两个不同的自动装载函数,分别是spl_autoload和spl_autoload_call,通过将autoload_fun指向这两个不同的加载函数地址可以实现不同的自动加载机制。

  • spl_autoload

  spl_autoload是SPL实现的默认的自动加载函数,是一个可以接受两个参数的函数。其中第一个函数为$class_name,表示要加载的类名;第二个参数是$file_extension为可选参数,表示类文件的扩展名。$file_extension中可以指定多个扩展名,扩展名之间用分号隔开即可,不指定扩展名,则使用默认的扩展名.inc或者.php。spl_autoload首先将$class_name变为小写,然后在所有的include_path中搜索$ class_name.inc或者$class_name.php文件。如果找到对应的文件,就加载对应的类。其实可以手动的使用spl_autoload("xxxx",".php")来实现xxxx类的加载。这其实和require/include差不多,但是,spl_autoload相对来说灵活一点,因为可以指定多个扩展名。

  前面说到,spl_autoload_register中包含的函数指针autoload_func用于指定要使用的加载函数。那么,我们必须将对应的函数地址赋值给autoload_func,spl_autoload_register()正实现了给函数指针autoload_func赋值的功能。如果spl_autoload_register()函数中不含有任何的参数,则默认是将spl_autoload()赋值给autoload_func.

  • spl_autoload_call 

  SPL模块的内部其实还存在着一个autoload_functions,其本质上是一个哈希表,或者为了直观的理解,我们将其想像成一个容器,里面的各个元素都是指向加载函数的指针。spl_autoload_call的实现机制其实也比较简单,按照一定的顺序遍历这个容器,执行里面的函数指针指向的加载函数,每执行一个指针之后都会检查所需要的类是否已经完成加载。如果完成了加载,则退出。否则继续接着向下执行。如果执行完所有的加载函数之后,所需要的类仍然没有完成加载,则spl_autoload_call()直接退出。这也就是说即使使用了autoload机制,也不一定能够完成类的加载,其关键在于看你如何创建你的自动加载函数。

  既然,存在一个autoload_functions,那么如何将创建的自动加载函数添加到其中呢?spl_autoload一样,同样使用spl_autoload_register()将加载函数注册到autoload_functions中。当然可以通过spl_autoload_unregister()函数将已经注册的函数从autoload_functions从哈希表中删除。这和前面所写过的工厂设计模式是一致的,详见:http://www.cnblogs.com/yue-blog/p/5771352.html。

  这里需要说明的一点是spl_autoload_register实现自动加载的顺序。spl_autoload的自动加载顺序为:首先判断autoload_func是否为空,如果autoload_func为空,则查看是否定义了__autoload函数,如果没有定义,则返回,并报错;如果定义了__autoload()函数,则返回加载的结果。如果autoload_func不为空,直接执行autoload_func指针指向的函数,不会检查__autoload是否定义。也就是说优先使用spl_autoload_register()注册过的函数。

  根据以上介绍,如果autoload_func为非空是就不能自动执行__autoload()函数了。如果想在使用spl_autoload_register()函数的情况下,依然可以使用__autoload()函数,则可以将__autoload函数通过spl_autoload_register()添加到哈希表中,即,spl_autoload_register(__autoload())

以上摘取网友博客内容,介绍spl_autoload_register的工作流程


回归主题,我们在小项目,用__autoload()就能实现基本的自动加载了

但是如果一个项目过大,或者需要不同的自动加载来加载不同路径的文件,这个时候__autoload就悲剧了

原因是一个项目中仅能有一个这样的 __autoload() 函数,因为 PHP 不允许函数重名,

也就是说你不能声明2个__autoload()函数文件,否则会报致命错误

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

sql_autoload_resister($param) 这个参数可以有多种形式:
sql_autoload_resister('load_function'); //函数名sql_autoload_resister(array('load_object', 'load_function')); //类和静态方法sql_autoload_resister('load_object::load_function'); //类和方法的静态调用

<?phpfunction load($className){    require $className . '.php';}spl_autoload_register('load'); //将load1函数注册到自动加载队列中。$db = new A(); //找不到A类,就会自动去调用刚注册的load函数了//php 5.3之后,也可以像这样支持匿名函数了。spl_autoload_register(function($className){    if (is_file($className . '.php')) {        require $className . '.php';    }});


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

<?phpclass autoloading{    //必须是静态方法,不然报错    public static function load($className)    {        require $className . '.php';    }}//2种方法都可以spl_autoload_register(array('autoloading','load'));spl_autoload_register('autoloading::load');$db = new A(); //会自动找到

需要注意的是,如果你同时使用spl_autoload_register和__autoload,__autoload会失效!!!

2.3、多个spl_autoload_register的使用

spl_autoload_register是可以多次重复使用的,这一点正是解决了__autoload的短板,

那么如果一个页面有多个,执行顺序是按照注册的顺序,一个一个往下找,如果找到了就停止


2.4、spl_autoload_functions

<?phpclass autoloading{    //必须是静态方法,不然报错    public static function load($className)    {        require $className . '.php';    }}//2种方法都可以spl_autoload_register(array('autoloading','load'));spl_autoload_register('autoloading::load');var_dump(spl_autoload_functions());//数组的形式输出array (size=1)  0 =>     array (size=2)      0 => string 'autoloading' (length=11)      1 => string 'load' (length=4)

3、spl_autoload_register搭配命名空间使用

自动加载现在是PHP现代框架的基石,基本都是spl_autoload_register来实现自动加载。namespace也是使用比较多的。所以spl_autoload_register + namespace 就成为了一个主流

示例:

IAutoloader.php

<?php class IAutoloader{    private static $classMap = array();        public static function load($class) {        if(isset(self::$classMap[$class])){            return true;        }else{            $class = str_replace("\\", "/", $class);            $file = __DIR__.DIRECTORY_SEPARATOR.$class.".php";            if(is_file($file)){                require_once ("$file");                self::$classMap[$class] = $class;            }else{                return false;            }        }    }}

index.php

<?php//自定义模块加载require_once 'IAutoloader.php';//采用`命名空间`的方式注册。php 5.3 加入的//也必须是得是static静态方法调用,然后就像加载namespace的方式调用spl_autoload_register("IAutoloader::load");//调用Controlle目录下UserController.php$module = 'Controller\UserController';new $module();

4、同命名空间下的相互调用

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

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

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

new Lib\Db\MySQL();  //报错,提示说 D:\wamp\www\testphp\module\Lib\Lib\Db\MySQL.php is not exist

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

正确的做法是,如果是在同一个命名空间下平级的2个文件。可以直接调用,不用命名空间。
new MySQL(); //直接这样就可以了。new Db\MySQL(); //如果有个Db文件夹,就这样。

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

use Lib\Db\MySQL;new MySQL();

我想在 Lib\Db\MySQL.php 中调用 Lib\Register.php。怎么调用呢?
use Lib\Register;Register::getInstance();

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


原创粉丝点击