当前位置: 首页 > news >正文

PHP7内核剖析 学习笔记 第八章 命名空间

PHP 5.3引入了命名空间,允许不同命名空间下定义同名函数、类,从而解决不同库之间名称冲突问题。

8.1 概述

PHP命名空间只能隔离类、函数、常量、接口,不包括全局变量。

8.2 命名空间的定义

命名空间通过关键字namespace来声明:

// 方式一
// file: ns_define.php
namespace com\aa;const MY_CONST = 1234;
function my_func() { /* ... */ }
class my_class { /* ... */ }// 方式二
// file: another_ns_define.php
namespace com\aa {const MY_CONST = 1234;function my_func() { /* ... */ }class my_class { /* ... */ }
}

但同一文件中以上两种定义方式不能混用,只能出现一种。

如果一个文件中包含命名空间,那么命名空间必须在除了declare关键字外的所有代码前声明。可以在多个文件中声明一个命名空间,也可在一个文件中声明多个命名空间。

如果没有定义命名空间,则类、函数定义在全局空间。

命名空间的实现只是在名称上进行了补全,当声明一个命名空间后,接下来编译类、函数、常量时,会把其名字前加上命名空间作为前缀来存储。

namespace语法被编译为ZEND_AST_NAMESPACE类型的抽象语法树节点,它有两个子节点:child[0]为命名空间名称;child[1]为大括号定义方式时包裹的语句,如果不是大括号包裹的方式,则此子节点为空。

ZEND_AST_NAMESPACE节点由zend_compile_namespace()编译,其中会把FC(current_namespace)设为当前定义的命名空间名称,FC这个宏是CG(file_context),file_context是一个编译过程的辅助结构,其在编译抽象语法树前分配:
在这里插入图片描述
在这里插入图片描述
编译完namespace声明语句后,继续编译后面的语句,此后定义的函数、类、常量均属于此命名空间,直到遇到下一个namespace定义。

namespace中的编译过程:
1.类、函数的编译

正常类、函数的编译分两步:第一步生成一条ZEND_DECLARE_FUNCTION、ZEND_DECALRE_CLASS的opcode;第二步在整个脚本编译的最后执行zend_do_early_binding(),其中会执行第一步生成的opcode,正是这一步将类、函数分别注册到了EG(class_table)、EG(function_table)中。

生成ZEND_DECLARE_FUNCTION、ZEND_DECALRE_CLASS时会把函数名、类名的存储地址保存到操作数中,然后在zend_do_early_binding()中获取名字,并以名字作为key将其注册到EG(class_table)或EG(function_table)中。命名空间中定义的函数、类的名字加前缀的操作是在生成ZEND_DECLARE_FUNCTION、ZEND_DECALRE_CLASS时完成的。准确地说,名字加前缀是通过zend_prefix_with_ns()获取的,其中如果发现FC(current_namespace)非空,则将其名字前加上FC(current_namespace)作为前缀,接下来注册到EG(class_table)、EG(function_table)中的名字就是修改后的名字了。

2.常量的编译

同上,常量名通过zend_resolve_const_name函数获取,其中最终调用的也是zend_prefix_with_ns函数。

8.3 命名空间的使用

命名空间中的类、函数、常量使用时,可以直接在前面加上命名空间名称前缀来使用,如要使用前例ns_define.php文件中的MY_CONST:

include 'ns_define.php'
echo \com\aa\MY_CONST;

上例中以\开头的名称为完全限定名称,此外还有其他形式的名称:
1.非限定名称:如my_func()。使用时如果当前脚本中声明了命名空间,则被解析为该命名空间中的名字;否则按原始名称解析。

2.部分限定名称:包含命名空间前缀,但不以\开头,如aa\func()。使用时如果当前脚本没有使用use导入namespace,那么解析规则同非限定名称;如果使用了use,下面介绍。

8.3.1 use导入

使用命名空间中的名字时,需要每一处都加上namespace前缀,使用use关键字可将一个命名空间中的名字导入,从而使用这些名字时不需再加namespace前缀,此外,use还可以使用as关键字给namespace起一个别名。

// ns_define.php
namespace aa\bb\cc\dd;
const MY_CONST = 1234;

我们可在脚本中使用以下方式访问MY_CONST:

// 方式一
include 'ns_define.php'
use aa\bb\cc\ddecho dd\MY_CONST;
// 方式二
include 'ns_define.php'
use aa\bb\ccecho cc\dd\MY_CONST;
// 方式三
include 'ns_define.php'
use aa\bb\cc\dd as DDecho DD\MY_CONST;
// 方式四
include 'ns_define.php'
use aa\bb\cc as CCecho CC\dd\MY_CONST;

实现原理:编译器如果发现use语句,就将use关键字后面的命名空间名插入哈希表FC(imports)。哈希表的key是别名,如果没有别名,则key为use关键字后面的命名空间名中最后一个\后面的内容,如方式二使用cc作为key;哈希表的值就是use关键字后面的命名空间名。以方式二为例,访问cc\dd\MY_CONST时,会以要访问的名字的第一个\前的内容cc为key查找FC(imports),从而得到aa\bb\cc,然后拼成完整名字:
在这里插入图片描述
use还可以导入一个类名字,之后使用它时就不用加namespace前缀了,例如:

// ns_define.php
namespace aa\bb\cc\dd;
class my_class { /* ... */ }

导入类:

include 'ns_define.php'
use aa\bb\cc\dd\my_class// 直接使用类名
$obj = new my_class();

这种直接导入类名字的原理同上。从PHP 5.6起,use可以导入函数、常量,语法为use function xxxuse const xxx,原理大致同上,区别在于使用的哈希表不是FC(imports),而是FC(imports_function)、FC(imports_const):
在这里插入图片描述
另外,如果使用了namespace关键字作为名字的前缀,则表示使用当前脚本的命名空间,而非use导入的:

// a.php
namespace aa;
const MY_CONST = 1000;// b.php
namespace bb;
include 'a.php';
use aa;const MY_CONST = 2000;
echo namespace\MY_CONST; // 输出2000,用的是当前脚本的命名空间bb

编译use语句时,会把use导入的名称以别名或最后分节为key存到对应哈希表中。

使用命名空间中的名字时,会经历名字补全的步骤,各个类型补全名字的过程:
1.类名补全

类名在编译时通过zend_resolve_class_name()进行类名补全。以下是具体补全规则:
(1)使用namespace关键字

namespace\xxx\类名,表示使用当前命名空间,此时zend_resolve_class_name函数中的zend_prefix_with_ns函数的处理如下:如果当前脚本定义了namespace(有FC(current_namespace)),则将当前脚本的命名空间名替换namespace关键字。

(2)完全限定名称

此时不需补全。在zend_resolve_class_name函数中,以下两种情况被判定为是完全限定名称:一是类名以\开头;二是类名的类型为ZEND_NAME_FQ。

// 在语法分析时将类名标识为ZEND_NAME_FQ
$obj = new \aa\my_class();
// 在抽象语法树编译时根据字符判断
// 但此方法我测试时无法通过编译,我使用的版本是PHP 8.0.7 (cli),可能是版本原因
$obj = new "\aa\my_class()";

(3)部分限定名称

如果当前脚本使用use导入了命名空间,且使用的是部分限定名称,则会以类名的第一节为key检索FC(imports),如果有这个key,将value取出,然后拼上类名,如图8-2。

(4)非限定名称

以类名为key查找FC(imports),如果有这个key,则拼上命名空间前缀。

2.函数名补全

函数名在编译时由zend_resolve_function_name()进行补全。补全时优先去FC(imports_function)中查找,如果没找到且是部分限定名称时,再去FC(imports)中查找。

以下是具体补全规则:
(1)使用namespace关键字

与类名处理一致。

(2)完全限定名称

不需要补全。

(3)非限定名称

即只有函数名,此时会以小写函数名为key查找FC(imports_function),如果找到了key,直接返回value,此value保存的是完整的“命名空间+函数名”。

(4)部分限定名称

如果FC(imports_function)中未找到对应的key,且为部分限定名称,则会去FC(imports)中查找,过程与类的相同。

3.常量名补全

与函数名补全类似,但非限定名称时,查找的是FC(imports_const)哈希表,且大小写敏感。

8.3.2 动态用法

类似以下的动态用法只能使用完全限定名称,无法自动补全名字:

$class_name = "\aa\bb\my_class";
$obj = new $class_name;

相关文章:

  • 一些好用的Chrome 扩展程序
  • 【AI测试革命】第七期:AI性能测试的深度实践——从智能建模到自动化调优的全链路升级
  • 怎么判断一个Android APP使用了Tauri 这个跨端框架
  • uni-app(4):js语法、css语法
  • 云原生安全核心:云安全责任共担模型(Shared Responsibility Model)详解
  • Java设计模式:探索编程背后的哲学
  • 2步彻底卸载VScode
  • R语言学习--Day07--T分布与T检验
  • 26考研|高等代数:线性变换
  • Git使用说明
  • Git和Gitcode交互教程
  • 攻防世界 - MISCall
  • [论文阅读]Pandora: Jailbreak GPTs by Retrieval Augmented Generation Poisoning
  • opencv彩图-直方图均衡化
  • Git子模块原理与实战详解
  • 提升推理能力会丢失指令跟随的能力?——【论文阅读笔记】
  • 超简单 FishSpeech 本地部署
  • 【Flutter】多语言适配-波斯语RTL从右到左
  • 怎么判断一个Android APP使用了flutter 这个跨端框架
  • ITK-读取DICOM文件标签
  • 微信朋友圈做网站推广赚钱吗/百度一下你就知道下载
  • seo网络推广优化/seo搜索引擎优化课程总结
  • 网站建设添加汉语/东莞营销推广公司
  • 浪琴女士手表网站/制作网站的最大公司
  • 网站编辑是什么/引流推广
  • h5网站开发多少钱/南昌seo搜索优化