php的命名空间与自动加载实现方法


Posted in PHP onAugust 25, 2019

类的自动加载

引子

当我们在php代码中加载类时,我们必须要include或者require 某个类文件。

但遇到类似的情况,例如:

require "Class1.php";
require "Class2.php";
$boy = $_GET['sex'] = 0?true:false;
if($boy)
{
 $class1 = new Class1();
}else{
 $class2 = new Class2();
}

假如我们需要判断一个人的性别,如果是男的就实例化class1这个类,如果是女的就实例化class2这个类。那么问题来了:这段代码,每次我只需要执行一个实例化对象,然而我必须加载这两个类文件。

php对于这种问题提出了解决方案

spl_auto_register()

这个概念在 在php5.1中提出

spl_auto_register($autoload_function = null, $throw = true, $prepend = false)

函数包含3个参数

①autoload_function  这是一个函数【方法】名称,可以是字符串或者数组(调用类方法使用)。这个函数(方法)的功能就是,来把需要new 的类文件包含include(requeire)进来,这样new的时候就不会找不到文件了。其实就是封装整个项目的include和require功能。

② $throw 该参数指定当autoload_function无法注册时,spl_autoload_register()是否应引发异常。

③ 如果为true,那么spl_autoload_register()将在自动加载到文件前面,而不时在它后面。

用法

那么有了这个函数之后向这样写了

function load($class)
{
 require "./{$class}.php";
}
spl_autoload_register('load');
if($boy)
{
 $class1 = new Class1();
}else{
 $class2 = new Class2();
}

程序执行过程如下:

// 正常的流程
new 一个对象-->找不到对象--> 报错

// 引入spl_autoload_register 后
new 一个对象-->找不到对象--> spl_autoload_register对说交给我试试--> 加载成功

加载之后我们执行了load这个函数,通过class的拼接,我们完成了加载函数的过程

__autoload()

类的自动加载在前面我们讲 spl_autoload_register 的时候已经和大家讲过了。今天我们讲另一种
__autoload() 在php7中已经不建议使用了

php的__autoload函数是一个魔术函数,在这个函数出现之前,如果一个php文件里引用了100个对象,那么这个文件就需要使用include或require引进100个类文件,这将导致该php文件无比庞大。于是就有了这个 __autoload函数。

__autoload函数在什么时候调用呢?当php文件中使用了new关键字实例化一个对象时,如果该类没有在本php文件中被定义,将会触发__autoload函数,此时,就可以引进定义该类的php文件,而后,就能实例化成功了。

(注意:如果需要实例化的对象,在本文件中已经找到该类的定义的话,就不会触发 __autoload 函数)

他和 spl_autoload_registe r的区别就在于当文件中同时出现__autoload和spl_autoload_register时,以spl_autoload_register为准

命名空间

我们先前讲过类的自动加载,然后我就在思索。

我们用框架写代码的时候,每在另一个文件中调用其他类时

我们并没有写spl_autoload_register这个方法啊?那我们时怎么实现的呢?

原理

原来啊,我们php在5.3时引入了命名空间的概念(这也是为什么大多数的框架不支持5.3之前的版本原因之一),命名空间大家多少还是了解的吧:不知道的去墙角面壁思过

命名空间简而言之就是一种标识,它的主要目的是解决命名冲突的问题。就像在日常生活中,有很多姓名相同的人,如何区分这些人呢?那就需要加上一些额外的标识。把工作单位当成标识似乎不错,这样就不用担心 “撞名” 的尴尬了。

命名空间分类

  • 完全限定命名空间
  • 限定命名空间
new 成都\徐大帅(); // 限定类名
new \成都\徐大帅(); // 完全限定类名

在当前命名空间没有声明的情况下,限定类名和完全限定类名是等价的。因为如果不指定空间,则默认为全局()。

namespace 美国;

new 成都\徐大帅(); // 美国\成都\徐大帅(实际结果)
new \成都\徐大帅(); // 成都\徐大帅(实际结果)

这个例子展示了在命名空间下,使用限定类名和完全限定类名的区别。(完全限定类名 = 当前命名空间 + 限定类名)

/* 导入命名空间 */
use 成都\徐大帅;
new 徐大帅(); // 成都\徐大帅(实际结果)

/* 设置别名 */
use 成都\徐大帅 AS CEO;
new CEO(); // 成都\徐大帅(实际结果)

/* 任何情况 */
new \成都\徐大帅();// 成都\徐大帅(实际结果)

使用命名空间只是让类名有了前缀,不容易发生冲突,系统仍然不会进行自动导入。

如果不引入文件,系统会在抛出 "Class Not Found" 错误之前触发 __autoload() 或者spl_autoload_register函数,并将限定类名传入作为参数。

上面的例子都是基于你已经将相关文件手动引入的情况下实现的,否则系统会抛出 " Class '成都徐大帅' not found"。因为她不知道这个文件在哪里。所以在引入命名空间以后又引入了自动加载

接下来,我们就在用命名空间加载我们的 类

一个使用命名空间自动加载类的小实验

首先,我们在一个新文件中定义

//School.php
namespace top;

class School
{
 function __construct()
 {
 echo '这是'.__CLASS__.'类的实现';
 }
}

这当然不是重要的,重要的是我们调用他的函数。我们在同一个目录建立一个index.php文件(不同文件也行,只要你写好映射关系)

//index.php

spl_autoload_register(function ($class){
 //从我们的 class名称中找,有没有对应的路径
 $map = [
 'top\\School'=>'./School.php'
 ];

 $file = $map[$class];
 //查看对应的文件是否存在
 if (file_exists($file))
 include $file;
});
echo "开始<br/>";
new top\School();

结果

开始
这是top\School类的实现

我们使用了 类名和类地址的映射关系,实现了我们的自动加载。然而这也意味着我们每次添加文件,就必须去更新我们的映射文件。在一个大型系统中这样数组维持的映射关系无疑很麻烦。那么有没有好一点的做法呢?

PSR4 自动加载规范

不知道的童鞋,可以看这里

PSR4 中文文档

PSR4 的具体解释

下面摘自上面链接,我觉得上面两篇文章已经讲得很透彻了

\<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>

PSR-4 规范中必须要有一个顶级命名空间,它的意义在于表示某一个特殊的目录(文件基目录)。子命名空间代表的是类文件相对于文件基目录的这一段路径(相对路径),类名则与文件名保持一致(注意大小写的区别)。

举个例子:在全限定类名 appviewnewsIndex 中,如果 app 代表 C:Baidu,那么这个类的路径则是 C:BaiduviewnewsIndex.php

我们就以解析 appviewnewsIndex 为例,编写一个简单的 Demo:

$class = 'app\view\news\Index';

/* 顶级命名空间路径映射 */
$vendor_map = array(
 'app' => 'C:\Baidu',
);

/* 解析类名为文件路径 */
$vendor = substr($class, 0, strpos($class, '\\')); // 取出顶级命名空间[app]
$vendor_dir = $vendor_map[$vendor]; // 文件基目录[C:\Baidu]
$rel_path = dirname(substr($class, strlen($vendor))); // 相对路径[/view/news]
$file_name = basename($class) . '.php'; // 文件名[Index.php]

/* 输出文件所在路径 */
echo $vendor_dir . $rel_path . DIRECTORY_SEPARATOR . $file_name;

通过这个 Demo 可以看出限定类名转换为路径的过程。那么现在就让我们用规范的面向对象方式去实现自动加载器吧。

首先我们创建一个文件 Index.php,它处于 appmvcviewhome 目录中:

namespace app\mvc\view\home;

class Index
{
 function __construct()
 {
  echo '<h1> Welcome To Home </h1>';
 }
}

接着我们在创建一个加载类(不需要命名空间),它处于 目录中:

class Loader
{
 /* 路径映射 */
 public static $vendorMap = array(
  'app' => __DIR__ . DIRECTORY_SEPARATOR . 'app',
 );

 /**
  * 自动加载器
  */
 public static function autoload($class)
 {
  $file = self::findFile($class);
  if (file_exists($file)) {
   self::includeFile($file);
  }
 }

 /**
  * 解析文件路径
  */
 private static function findFile($class)
 {
  $vendor = substr($class, 0, strpos($class, '\\')); // 顶级命名空间
  $vendorDir = self::$vendorMap[$vendor]; // 文件基目录
  $filePath = substr($class, strlen($vendor)) . '.php'; // 文件相对路径
  return strtr($vendorDir . $filePath, '\\', DIRECTORY_SEPARATOR); // 文件标准路径
 }

 /**
  * 引入文件
  */
 private static function includeFile($file)
 {
  if (is_file($file)) {
   include $file;
  }
 }
}

最后,将 Loader 类中的 autoload 注册到 spl_autoload_register 函数中:

include 'Loader.php'; // 引入加载器
spl_autoload_register('Loader::autoload'); // 注册自动加载

new \app\mvc\view\home\Index(); // 实例化未引用的类

/**
 * 输出: <h1> Welcome To Home </h1>
 */

示例中的代码其实就是 ThinkPHP 自动加载器源码的精简版,它是 ThinkPHP 5 能实现惰性加载的关键。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

PHP 相关文章推荐
php基础知识:函数基础知识
Dec 13 PHP
php 404错误页面实现代码
Jun 22 PHP
PHP源码之 ext/mysql扩展部分
Jul 17 PHP
解析php入库和出库
Jun 25 PHP
页面利用渐进式JPEG来提升用户体验度
Dec 01 PHP
php获取twitter最新消息的方法
Apr 14 PHP
给PHP开发者的编程指南 第一部分降低复杂程度
Jan 18 PHP
PHP实现统计在线人数功能示例
Oct 15 PHP
PHP实现对数组分页处理实例详解
Feb 07 PHP
PHP简单实现合并2个数字键数组值的方法
May 30 PHP
PHP排序算法之堆排序(Heap Sort)实例详解
Apr 21 PHP
PHP设计模式(观察者模式)
Jul 07 PHP
PHP7数组的底层实现示例
Aug 25 #PHP
PHP实现cookie跨域session共享的方法分析
Aug 23 #PHP
php常用经典函数集锦【数组、字符串、栈、队列、排序等】
Aug 23 #PHP
php中错误处理操作实例分析
Aug 23 #PHP
php+js实现的无刷新下载文件功能示例
Aug 23 #PHP
php简单检测404页面的方法示例
Aug 23 #PHP
PHP Redis扩展无法加载的问题解决方法
Aug 22 #PHP
You might like
php smarty函数扩展
2010/03/15 PHP
PHP运行时强制显示出错信息的代码
2011/04/20 PHP
php将mysql数据库整库导出生成sql文件的具体实现
2014/01/08 PHP
iOS10推送通知开发教程
2016/09/19 PHP
PHP流Streams、包装器wrapper概念与用法实例详解
2017/11/17 PHP
php删除一个路径下的所有文件夹和文件的方法
2018/02/07 PHP
PHP7.1实现的AES与RSA加密操作示例
2018/06/15 PHP
gearman中worker常驻后台,导致MySQL server has gone away的解决方法
2020/02/27 PHP
深入理解PHP+Mysql分布式事务与解决方案
2020/12/03 PHP
XHTML下,JS浮动代码失效的问题
2009/11/12 Javascript
JS 文件传参及处理技巧分析
2010/05/13 Javascript
Jquery 例外被抛出且未被接住原因介绍
2013/09/04 Javascript
js模拟点击以提交表单为例兼容主流浏览器
2013/11/29 Javascript
JavaScript函数模式详解
2014/11/07 Javascript
javascript中Math.random()使用详解
2015/04/15 Javascript
jQuery控制frames及frame页面JS的方法
2016/03/08 Javascript
深入理解JQuery循环绑定事件
2016/06/02 Javascript
解决vue中无法动态修改jqgrid组件 url地址的问题
2018/03/01 Javascript
关于自定义Egg.js的请求级别日志详解
2018/12/12 Javascript
js实现左右轮播图
2020/01/09 Javascript
微信小程序手动添加收货地址省市区联动
2020/05/18 Javascript
jQuery实现鼠标拖拽登录框移动效果
2020/09/13 jQuery
python导入模块交叉引用的方法
2019/01/19 Python
python内存管理机制原理详解
2019/08/12 Python
Python使用random模块生成随机数操作实例详解
2019/09/17 Python
python 实现简单的FTP程序
2019/12/27 Python
基于SpringBoot构造器注入循环依赖及解决方式
2020/04/26 Python
python 瀑布线指标编写实例
2020/06/03 Python
我的applet原先好好的, 一放到web server就会有问题,为什么?
2016/05/10 面试题
中秋节主持词
2014/04/02 职场文书
任命书格式
2014/06/05 职场文书
条幅标语大全
2014/06/20 职场文书
介绍信范文
2015/01/31 职场文书
中小学生安全教育观后感
2015/06/17 职场文书
暑期家教宣传单
2015/07/14 职场文书
2016党员干部廉洁自律心得体会
2016/01/13 职场文书