PHP 理解 autoload 、PSR-0、PSR-4 的因缘并分析 PS0-0 与 PSR-4 的差异到底在哪里

2023-10-24 12:50

本文主要是介绍PHP 理解 autoload 、PSR-0、PSR-4 的因缘并分析 PS0-0 与 PSR-4 的差异到底在哪里,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. include & require

我们知道一个 A.php 文件若想引入 B.php 文件里的类,就需要通过 include / require 的方式将 B.php 引入。
这种方式对小项目来说没啥问题,但对大型项目来说,通常会包含很多公共文件,比如:Foo/Bar/Dog.php,按照传统方式我们在每个所需的地方将这个文件引入即可,但这样会造成如下问题:

  1. 每个地方都要引入 Foo/Bar/Dog.php ,操作实在繁琐
  2. 代码量增多
  3. 重复粘贴容易出现残漏情况

那有没有办法解决这个问题呢?有!__autoload 就是用来解放 include / require 的。

2. __autoload

__autoload 是 php 5 以后新增的一个魔法函数,此函数在使用 new xxx 时自动触发,并传递一个 $class 的参数,这个参数就是 new xxx 中的 xxx 部分, 下面是它的用法

function __autoload($class_name) {# $class = Foo\Bar\Dogrequire_once $class_name . '.php';
}
$dog = new Foo\Bar\Dog();
$dog->say();

这个函数帮我们减少了许多的 include/require,但由于 __autoload 只能使用一次,假设我们不止有 Foo/Bar 这个目录,还有 Coo/TooAoo/Boo 等共用目录这可怎么办? 有没有办法能让多次加载呢?有! sql_autoload_register 就是来解决这个问题的。

3. sql_autoload_register

sql_autoload_register 专门用来定义多个 __autoload 的函数,它的用法如下:

function my_autoload_1($class_name) {require_once $class_name . '.php';
}
function my_autoload_2($class_name)  {require_once $class_name . '.php';
}
function my_autoload_3($class_name) {require_once $class_name . '.php';
}
sql_autoload_register('my_autoload_1');
sql_autoload_register('my_autoload_2');
sql_autoload_register('my_autoload_3');
$dog1 = new Foo\Bar\Dog();
$dog1->say();
$dog2 = new Coo\Too\Dog();
$dog2->say();
$dog3 = new Foo\Bar\Dog();
$dog3->say();

现在多个自动加载的问题解决了,由于sql_autoload_register 既能代替 __autoload 也能实现多个 __autoload ,所以 __autoload 自然也就被 PHP 官方淘汰了。
然后这就完了吗?并没有~

每个人都可以用 sql_autoload_register 定义自己的自动加载器,而每个人的写法又是不同的,若第三方插件/框架的作者们都实现自家的自动加载器,当我们使用这些插件/框架时就得熟悉它们的引入语法,对开发者的学习成本增加了许多,后来就有了一群志同道合的人联合起来要搞一个自动加载器的规范,而这个规范就叫做 PSR-0 ,全称是 PHP Standard Recommend,大家需要统一按照这种规范来写出自己的自动加载器才算合格。

4. PSR-0

PSR-0 的规范这里我就不细说,有意者可以参考官方文档 PSR-0
这里我们重点关注实现了 PSR-0 自动加载器后写法是怎么样的?或者说,哪些比较流行的框架帮我们写好了一个 PSR-0 规范的自动加载器? 要怎么使用?这里我们就以 composer 为例子,假设我们的项目结构如下:

srcFooBarDog.phpCooTooDog.php
test.php

接着参考 composer 文档需要在 composer.json 里进行映射配置

{"name": "cookcyq","autoload": {"psr-0": {"Foo\\Bar": "src/","Coo\\Too": "src/"}}
}

配置后需要执行: composer durmp-auto -o 它会自动在 vendor/composer/ 下生成 autoload.php 文件,我们引入这个文件就可以使用愉快的使用自动加载器了。

// test.php
require_once "vendor/composer/autoload.php";
// 提示:
// PSR-0 规范里支持 _ 下划线语法,它最终会被替换成 / ,所以下面是等价的。
$dog1 = new Foo_Bar_Dog();
$dog2 = new Foo\Bar\Dog();$dog3 = new Coo\Too_Dog();
$dog4 = new Coo\Too\Dog();

有的小伙伴可能疑惑了,为什么会支持下划线 _ 这种形式呢?这是为了起到独立作用域作用,避免有重复名字冲突,因为 namespace 在那时还没出现呢。
嗯,到了 PSR-0 就结束了吗?然鹅并没有,这不 namespace 在不久后就出现了。
所以这才有了后来的 PSR-4 新的规范。

5. PSR-4

我认为 PSR-4 完全就是因为有了 namespace 这玩意才诞生出的新规范。
如果你还不知道什么是 namespace 可以参考我前面写过的 PHP & 理解 Namespace (命名空间)

PSR-4 与 PSR-0 有什么不同呢?

  1. PSR-4 不支持 _ 下划线这种写法,因为已经有了 namespace
  2. composer.json 中 key 的结尾必须要带上 \\ ,如下
{"name": "cookcyq","autoload": {"psr-0": {"Foo\\Bar": "src/","Coo\\Too": "src/"},"psr-4": {"Foo\\Bar\\": "src/","Coo\\Too\\": "src/"}}
}

看到这里,相信你已经懂得 autoload 这个自动加载的概念以及如何在 composer 中使用它们的自动加载了,对于时间匆忙的同学也可以不用往下看,我认为这已经够用了。

如果时间充裕的话可以接着往下读。

6. 为什么 composer 不直接拥抱 PSR-4 还要兼容 PSR-0 ?

答案很明显,目前有些古老且有用的插件作者采用的还是 PSR-0 规范,
其中有些作者用的是 _ 下划线语法,所以 composer 不能一刀切。

7. composer 的 PSR-0 和 PSR-4 实现方式有啥不同?

对于新手(包括我)来说,常常找到的 PSR-0 与 PSR-4 的解释很令人疑惑。
在这里插入图片描述

在这里插入图片描述

图中的PSR-0 映射关系我能看懂,但 PSR-4 我是一脸懵逼。假设 Bar.php 文件就放到 src/Acme/Foo/Bar.php 里面,但 PSR-4 的 Acme\Foo => /src/Bar.php/src/Bar.php 的这种映射关系肯定找不到 Bar.php 文件啊?于是本着好奇心便各种搜索,结果还是令人失望,大部分要么都是搬官方的例子要么都是复制别人的过来然后也不说明为什么会这样的关系,至少对我来说,这种解释是行不通的。目前我安慰自己的方式是:底层会自动帮我们找到完整的路径进行引入,然后这个映射关系不是指上面的引入文件关系,这样我心里才舒服些,于是我就在想,与其这么找,倒不如去看看源码到底是怎么帮我们引入最终的文件的,于是就有了接下来的源码分析,放心,我这里仅仅摘取最关键部分,因为其它的我也看不懂。

8. 分析 composer PSR-0 & PSR-4 实现原理

这是本案例的完整目录结构
在这里插入图片描述

假设 composer.json 采用 PSR-4 ,内容如下

{"name": "cookcyq","license": "n","require": {},"autoload": {"psr-4": {"Foo\\Bar\\": "vendor/foo/bar/src"}}
}

使用 comopser durmp-auto -o 生成以下文件:
在这里插入图片描述

我们重点关注里面的 ClassLoader.php 这个文件,里面包含了 PSR-0 和 PSR-4 的几个核心关键实现自动加载的属性和方法

关键属性:

class ClassLoader {// PSR-4 关键属性private $prefixLengthsPsr4 = array();private $prefixDirsPsr4 = array();// PSR-0 关键属性private $prefixesPsr0 = array();private $fallbackDirsPsr0 = array();

关键方法1 add / addPsr4():将 composer.json 里的 "psr-0": {... } 和 psr-4": {... } 内容添加关键属性里面,源码如下

public function add($prefix, $paths, $prepend = false) { ...代码与 addPsr4 差不多 }
public function addPsr4($prefix, $paths, $prepend = false){if (!$prefix) {// ...} elseif (!isset($this->prefixDirsPsr4[$prefix])) {$length = strlen($prefix);// 结尾必须添加 \\ if ('\\' !== $prefix[$length - 1]) {throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");}$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;$this->prefixDirsPsr4[$prefix] = (array) $paths;} elseif ($prepend) {// Prepend directories for an already registered namespace.$this->prefixDirsPsr4[$prefix] = array_merge((array) $paths,$this->prefixDirsPsr4[$prefix]);} else {// Append directories for an already registered namespace.$this->prefixDirsPsr4[$prefix] = array_merge($this->prefixDirsPsr4[$prefix],(array) $paths);}}

你可以不用看上面的代码,只需关心最终存储结构类似为:

// PSR-4==========================
$prefixLengthsPsr4 = ["F" => ["Foo\\Bar\\" => 6,]
]
$prefixDirsPsr4 = ["Foo\\Bar\\" => ["vendor/foo/bar/src"]
]// PSR-0==========================
$prefixesPsr0 = ["F" => ["Foo\\Bar\\" => ["vendor/foo/bar/src"]]
]

关键方法2 findFileWithExtension(): 查找 PSR-0 和 PSR-4 的完整文件路径就是在这里完成的,当找到后就将其 includeFile 引入即可,整个 autoload 基本流程就是这样,关键源码如下:

private function findFileWithExtension($class, $ext){$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;$first = $class[0];// ============================ PSR-4  查询 =============================================if (isset($this->prefixLengthsPsr4[$first])) {$subPath = $class;while (false !== $lastPos = strrpos($subPath, '\\')) {$subPath = substr($subPath, 0, $lastPos);$search = $subPath . '\\';if (isset($this->prefixDirsPsr4[$search])) {$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);foreach ($this->prefixDirsPsr4[$search] as $dir) {if (file_exists($file = $dir . $pathEnd)) {return $file;}}}}}// ============================ PSR-0  查询 =============================================// 支持下划线的条件语句if (false !== $pos = strrpos($class, '\\')) {$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1). strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);} else {$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;}if (isset($this->prefixesPsr0[$first])) {foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {if (0 === strpos($class, $prefix)) {foreach ($dirs as $dir) {if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {return $file;}}}}}return false;}

上面的代码我们只需要找到最关键的两层循环:
PSR-4 采用: while + foreach
PSR-0 采用: foreach + foeach
因为 PSR-4 不需要 _ 下划线,以及结尾必须要带上 \\ ,势必要用另外一种方式来实现找到文件路径结构,才有了这两个属性: $prefixLengthsPsr4 / $prefixDirsPsr4,然后再结合 whre +foreach 来寻找完整路径,而 PSR-0 只定义了 $prefixesPsr0 属性,所以采用了 foreach + foeach 来寻找完整路径,最终这两种方式都成功找出完整路径。

9. 好了,做个总结吧

  1. PSR-0 支持 _ 下划线,PSR-4 不支持
  2. composer.json PSR-0 后面不用加 \\,PSR-4 后面必须加 \\

剩下的在 composer.json 配置用法是完全一致的。只是查找完整文件路径方式采用不同的循环策略。

参考文献:
https://stackoverflow.com/questions/24868586/what-are-the-differences-between-psr-0-and-psr-4#:~:text=The%20summary%20is%20that%20PSR,part%20following%20the%20anchor%20point.
https://my.oschina.net/sallency/blog/893518

这篇关于PHP 理解 autoload 、PSR-0、PSR-4 的因缘并分析 PS0-0 与 PSR-4 的差异到底在哪里的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/275277

相关文章

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud

线性因子模型 - 独立分量分析(ICA)篇

序言 线性因子模型是数据分析与机器学习中的一类重要模型,它们通过引入潜变量( latent variables \text{latent variables} latent variables)来更好地表征数据。其中,独立分量分析( ICA \text{ICA} ICA)作为线性因子模型的一种,以其独特的视角和广泛的应用领域而备受关注。 ICA \text{ICA} ICA旨在将观察到的复杂信号

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分