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 遍历数据表数据并列表横向排列的代码
Sep 05 PHP
火车头discuz6.1 完美采集的php接口文件
Sep 13 PHP
php access 数据连接与读取保存编辑数据的实现代码
May 12 PHP
显示youtube视频缩略图和Vimeo视频缩略图代码分享
Feb 13 PHP
PHP中redis的用法深入解析
Feb 20 PHP
跟我学Laravel之配置Laravel
Oct 15 PHP
PHP+Mysql+jQuery文件下载次数统计实例讲解
Oct 10 PHP
PHP+原生态ajax实现的省市联动功能详解
Aug 15 PHP
php实现有序数组旋转后寻找最小值方法
Sep 27 PHP
PHP使用mongoclient简单操作mongodb数据库示例
Feb 08 PHP
Yii2框架视图(View)操作及Layout的使用方法分析
May 27 PHP
解决laravel session失效的问题
Oct 14 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取得一个类的属性和方法的实现代码
2011/05/22 PHP
PHP下载大文件失败并限制下载速度的实例代码
2019/05/10 PHP
javascript中的有名函数和无名函数
2007/10/17 Javascript
Javascript select控件操作大全(新增、修改、删除、选中、清空、判断存在等)
2008/12/19 Javascript
jquery 插件 任意位置浮动固定层
2008/12/25 Javascript
JavaScript 高效运行代码分析
2010/03/18 Javascript
js实现网页标题栏闪烁提示效果实例分析
2014/11/20 Javascript
javascript中基本类型和引用类型的区别分析
2015/05/12 Javascript
在页面中输出当前客户端时间javascript实例代码
2016/03/02 Javascript
jquery zTree异步加载、模糊搜索简单实例分享
2016/03/24 Javascript
关于cookie的初识和运用(js和jq)
2016/04/07 Javascript
JavaScript事件详细讲解
2016/06/27 Javascript
15位和18位身份证JS校验的简单实例
2016/07/18 Javascript
微信小程序 页面跳转传参详解
2016/10/28 Javascript
详解使用vue实现tab 切换操作
2017/07/03 Javascript
jQuery实现鼠标响应式淘宝动画效果示例
2018/02/13 jQuery
微信小程序缓存过期时间的使用详情
2019/05/12 Javascript
vue中js判断长时间不操作界面自动退出登录(推荐)
2020/01/22 Javascript
使用React代码动态生成栅格布局的方法
2020/05/24 Javascript
Vue实现boradcast和dispatch的示例
2020/11/13 Javascript
Python中操作MySQL入门实例
2015/02/08 Python
python SSH模块登录,远程机执行shell命令实例解析
2018/01/12 Python
Tensorflow使用tfrecord输入数据格式
2018/06/19 Python
python 文件转成16进制数组的实例
2018/07/09 Python
python将.ppm格式图片转换成.jpg格式文件的方法
2018/10/27 Python
Python控制Firefox方法总结
2019/06/03 Python
Python装饰器实现方法及应用场景详解
2020/03/26 Python
python3+selenium获取页面加载的所有静态资源文件链接操作
2020/05/04 Python
Python json格式化打印实现过程解析
2020/07/21 Python
Python 实现国产SM3加密算法的示例代码
2020/09/21 Python
红旗团支部事迹材料
2014/01/27 职场文书
高中军训感言800字
2014/03/05 职场文书
大学生作弊检讨书
2014/09/11 职场文书
2014年文员工作总结
2014/11/18 职场文书
Redis入门教程详解
2021/08/30 Redis
进阶篇之linux环境下安装MySQL数据库
2022/04/09 MySQL