PHP的自动加载机制

PHP的自动加载机制

    最近尝试着写一个简单的php框架,来加深对mvc的认识,其中类的载入是首先要解决的问题。这篇文章,让我们追根溯源,看看类的加载如何从最原始的做法然后一步一步改进,到现在形成比较成熟的做法。来对类的载入有一个更加深刻的认识。

    1、面向过程式的做法

    在 PHP 开发过程中,如果希望从外部引入一个 Class,通常会使用include和require方法,去把定义这个 Class 的文件包含进来。

    

    这个在小规模开发的时候,没什么大问题。但在大型的开发项目中,使用这种方式会存在一些问题:

    1)如果一个 PHP 文件需要使用很多其它类,那么就需要很多的 require/include 语句,这样有可能会造成遗漏或者包含进不必要的类文件

    2)如果大量的文件都需要使用其它的类,那么要保证每个文件都包含正确的类文件肯定是一个噩梦, 况且 require或 include 的性能代价很大。

    那么,如何解决这个问题呢?

    PHP5 为这个问题提供了一个解决方案,这就是类的自动加载(autoload)机制。autoload机制可以使得 PHP程序有可能在使用类时才自动包含类文件,而不是一开始就将所有的类文件include进来,这种机制也称为 Lazy loading (惰性加载)。

    2、__autoload(自动加载函数)

     当我们在使用一个类时,如果发现这个类没有加载,就会自动运行 __autoload() 函数,这个函数是我们在程序中自定义的,在这个函数中我们可以加载需要使用的类。

    参考代码:

 1 Tom.php
 2 <?php
 3 class Tom
 4 {
 5     public function say()
 6     {
 7         echo "Hello Tom." . PHP_EOL;
 8     }
 9 }
10 ?>
11 
12 Lily.php
13 <?php
14 class Lily
15 {
16     public function say()
17     {
18         echo 'Hello Lily.' . PHP_EOL;
19     }
20 }
21 ?>
22 
23 autoload.php
24 <?php
25 
26 function __autoload($classname)
27 {
28     $filename = "./" . $classname . ".php";
29     echo $classname . PHP_EOL;
30     include_once($filename);
31 }
32 
33 $tom = new Tom();
34 $lily = new Lily();
35 echo $tom->say();
36 echo $lily->say();

运行结果:

    从上面的例子中,我们可以看出 __autoload 至少要做三件事情:

    1)根据类名确定类文件名

    2)确定类文件所在的磁盘路径

    3)将类从磁盘文件中加载到系统中   

    分析:第三步最简单,只需要使用 include / require 即可。要实现第一步,第二步的功能,必须在开发时约定类名与磁盘文件的映射方法只有这样我们才能根据类名找到它对应的磁盘文件

当有大量的类文件要包含的时候,我们只要确定相应的规则,然后在 __autoload() 函数中将类名与实际的磁盘文件对应起来,就可以实现 lazy loading 的效果 。

    不过这种方式,也存在一些问题:

    1)__autoload() 是全局函数只能定义一次 ,不够灵活。

    2)如果在一个系统的实现中,如果需要使用很多其它的类库,这些类库可能是由不同的开发人员编写的, 其类名与实际的磁盘文件的映射规则不尽相同。这时如果要实现类库文件的自动加载,就必须 在 __autoload() 函数中将所有的映射规则全部实现,这样的话 __autoload() 函数有可能会非常复杂,甚至无法实现。最后可能会导致 __autoload() 函数十分臃肿,这时即便能够实现,也会给将来的维护和系统效率带来很大的负面影响。

 

    3)php7.2已经弃用__autoload函数。

    那么如何来解决这个问题呢?

    答案就是使用一个 __autoload调用堆栈 ,不同的映射关系写到不同的 __autoload函数 中去,然后统一注册统一管理,这个就是 PHP5 引入的 SPL Autoload

    SPL是 Standard PHP Library(标准PHP库)的缩写。它是 PHP5 引入的一个扩展标准库,包括 spl autoload 相关的函数以及各种数据结构和迭代器的接口或类。

    3、spl_autoload_register

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

         2) 注册的函数会进入队列中,多次注册时,先进先出,加载成功则终止,加载失败会继续调用队列中下一个被注册的函数。

       参考代码1:

 1 <?php
 2 
 3 class MyClass {
 4   public static function autoload($className) {
 5     // ...
 6   }
 7 }
 8 
 9 spl_autoload_register(array('MyClass', 'autoload'));
10 ?>
11 
12 * Or you can use an instance :
13 <?php
14 class MyClass {
15   public function autoload($className) {
16     // ...
17   }
18 }
19 
20 $instance = new MyClass();
21 spl_autoload_register(array($instance, 'autoload'));
22 ?>

 参考代码2:

 1 Lily.php
 2 <?php
 3 
 4 class Lily
 5 {
 6     public function say()
 7     {
 8         echo 'Hello Lily.' . PHP_EOL;
 9     }
10 }
11 ?>
12 <?php
13 /**
14  * spl_autoload_register($autoload_function, true, false)
15  * 第一个参数(autoload_function):欲注册的自动装载函数。
16  * 第二个参数(throw):此参数设置了 autoload_function 无法成功注册时, spl_autoload_register()是否抛出异常。
17  * 第三个参数(prepend):当为true时,spl_autoload_register()函数会添加函数到队列之首,而不是队列尾部。
18  */
19 spl_autoload_register('autoload', true, true);
20 
21 /**
22  * 自定义类加载
23  * @param string $className
24  */
25 function autoload($className="") {
26     echo "类名为:autoload/".$className . PHP_EOL;
27     include "./{$className}.php";
28 }
29 
30 $Lily = new Lily();
31 $Lily->say();
32 
33 //运行结果:
34 //类名为:autoload/Lily
35 //Hello Lily.

    spl_autoload_register() 就是我们上面所说的__autoload调用堆栈,我们可以向这个函数注册多个我们自己的 autoload() 函数,当 PHP 找不到类名时,PHP就会调用这个堆栈,然后去调用自定义的 autoload() 函数,实现自动加载功能。如果我们不向这个函数输入任何参数,那么就会默认注册 spl_autoload() 函数。

4、spl_autoload_register+namespace命名空间

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

参考代码:

 1 Name.php
 2 <?php
 3 
 4 namespace Lib;
 5 
 6 class  Name
 7 {
 8     public static function test()
 9     {
10         echo '123';
11     }
12 }
13 ?>
14 loading.php
15 <?php
16 
17 namespace autoload;
18 
19 class loading
20 {
21     public static function autoload($className)
22     {
23         //根据PSR-O的第4点 把 \ 转换层(目录风格符) DIRECTORY_SEPARATOR ,
24         //便于兼容Linux文件找。Windows 下(/ 和 \)是通用的
25         //由于namspace 很规格,所以直接很快就能找到
26         $fileName = str_replace('\\', DIRECTORY_SEPARATOR, DIR . '\\' . $className) . '.php';
27         if (is_file($fileName)) {
28             require $fileName;
29         } else {
30             echo $fileName . ' is not exist';
31             die;
32         }
33     }
34 }
35 ?>
36 index.php
37 <?php
38 
39 //定义当前的目录绝对路径
40 define('DIR', dirname(__FILE__));
41 //加载这个文件
42 require DIR . '/loading.php';
43 //采用`命名空间`的方式注册。php 5.3 加入的
44 //也必须是得是static静态方法调用,然后就像加载namespace的方式调用,注意:不能使用use
45 spl_autoload_register("\\autoload\\loading::autoload");
46 // 调用三个namespace类
47 //定位到Lib目录下的Name.php
48 \Lib\Name::test();
49 
50 //运行结果:
51 //123

    总结一下,自动加载带来的好处:

    1)使用类之前无需 include/require

    2)使用类的时候才会 include/require文件,实现了 lazy loading , 避免了 include/require 多余文件。

    3)无需考虑引入类的实际磁盘地址 , 实现了逻辑和实体文件的分离

     下一篇文章《如何用composer来构建自己的MVC项目》中,我们尝试利用 Composer这个依赖管理工具来一步一步构建自己的 PHP 框架。

 

 

 

 

参考链接:

https://segmentfault.com/a/1190000014948542
https://www.zybuluo.com/phper/note/66447
https://www.php.net/manual/zh/function.spl-autoload-register.php

 

posted @ 2020-12-29 11:49  欢乐豆123  阅读(406)  评论(0编辑  收藏  举报