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 无限级 SelectTree 类
May 19 PHP
PHP 处理图片的类实现代码
Oct 23 PHP
PHP中使用unset销毁变量并内存释放问题
Jul 05 PHP
ThinkPHP中的系统常量和预定义常量集合
Jul 01 PHP
PHP中执行cmd命令的方法
Oct 11 PHP
PHP模拟asp.net的StringBuilder类实现方法
Aug 08 PHP
PHP中each与list用法分析
Jan 08 PHP
Zend Framework开发入门经典教程
Mar 23 PHP
php删除txt文件指定行及按行读取txt文档数据的方法
Jan 30 PHP
Redis构建分布式锁
Mar 28 PHP
php+mysql+jquery实现简易的检索自动补全提示功能
Apr 15 PHP
如何优雅的使用 laravel 的 validator验证方法
Nov 11 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
如何分别全角和半角以避免乱码
2006/10/09 PHP
PHP性能优化工具篇Benchmark类调试执行时间
2011/12/06 PHP
PHP中echo和print的区别
2014/08/28 PHP
通过源码解析Laravel的依赖注入
2018/01/22 PHP
用JavaScript获取网页中的js、css、Flash等文件
2006/12/20 Javascript
IE8 中使用加速器(Activities)
2010/05/14 Javascript
js修改table中Td的值(定义td的单击事件)
2013/01/10 Javascript
ExtJS中设置下拉列表框不可编辑的方法
2014/05/07 Javascript
JS限制文本框只能输入数字和字母方法
2015/02/28 Javascript
JavaScript表格常用操作方法汇总
2015/04/15 Javascript
JavaScript+html5 canvas制作的圆中圆效果实例
2016/01/27 Javascript
JS获取当前脚本文件的绝对路径
2016/03/02 Javascript
jquery 动态合并单元格的实现方法
2016/08/26 Javascript
JS简单实现tab切换效果的多窗口显示功能
2016/09/07 Javascript
react.js CMS 删除功能的实现方法
2017/04/17 Javascript
JavaScript中各数制转换全面总结
2017/08/21 Javascript
vue 2.0项目中如何引入element-ui详解
2017/09/06 Javascript
微信小程序MUI侧滑导航菜单示例(Popup弹出式,左侧滑动,右侧不动)
2019/01/23 Javascript
vue柱状进度条图像的完美实现方案
2019/08/26 Javascript
vue实现图片按比例缩放问题操作
2020/08/11 Javascript
vue-cli3项目打包后自动化部署到服务器的方法
2020/09/16 Javascript
Python中关键字is与==的区别简述
2014/07/31 Python
Python实现求最大公约数及判断素数的方法
2015/05/26 Python
python生成随机图形验证码详解
2017/11/08 Python
Python数组拼接np.concatenate实现过程
2020/04/18 Python
Python如何利用Har文件进行遍历指定字典替换提交的数据详解
2020/11/05 Python
python实现b站直播自动发送弹幕功能
2021/02/20 Python
美国波西米亚风格精品店:South Moon Under
2019/10/26 全球购物
节约用电倡议书
2015/04/28 职场文书
酒会开场白大全
2015/06/01 职场文书
催款函范文
2015/06/24 职场文书
基于python实现银行管理系统
2021/04/20 Python
python中if和elif的区别介绍
2021/11/07 Python
CSS font-variation 可变字体的魅力(实例详解)
2022/03/03 HTML / CSS
Python实现批量自动整理文件
2022/03/16 Python
Python几种酷炫的进度条的方式
2022/04/11 Python