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