当前位置: 首页 > 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;
http://www.xdnf.cn/news/606439.html

相关文章:

  • Python打卡DAY34
  • 亚马逊搜索代理: 终极指南
  • 线性回归中涉及的数学基础
  • 嵌入式学习笔记 - freeRTOS链表中pxIndex->pxPrevious 与pxIndex->pxPrevious->的区别
  • DB-GPT扩展自定义Agent配置说明
  • 微信小程序调用蓝牙API “wx.writeBLECharacteristicValue()“ 报 errCode: 10008 的解决方案
  • GMP模型入门
  • Lyra学习笔记1地图角色加载流程
  • 树莓派WiringPi库
  • 大模型「瘦身」指南:从LLaMA到MobileBERT的轻量化部署实战
  • php 根据另一个数组中 create_time 的时间顺序,对原始数组进行排序。
  • Neo4j入门第一期(Cypher入门)
  • RabbitMQ ⑥-集群 || Raft || 仲裁队列
  • CentOS 7.6 升级 Openssl 及 Openssh 方法文档
  • Unity EventCenter 消息中心的设计与实现
  • EasyExcel使用
  • GD32 IIC(I2C)通信(使用示例为SD2068)
  • 2.4g芯片引脚功能
  • 56 在standby待机打通uart调试的方法
  • 5.23本日总结
  • SDL2常用函数SDL事件处理:SDL_Event|SDL_PollEvent
  • Vue+css实现扫描动画效果(使用@keyframes scan)
  • RequestBody注解中Map
  • 为什么信号经过线束会有衰减?
  • AG32VH 系列应用指南
  • 嵌入式鸿蒙openharmony应用开发环境搭建与工程创建实现
  • Postgresql 数据库实例管理命令
  • Spring IoC容器初始化过程
  • 设计模式-结构型模式(详解)
  • el-dialog 组件 多层嵌套 被遮罩问题