Skip to content

Commit 63ddbbe

Browse files
committed
finish namespace
1 parent 325e0ef commit 63ddbbe

File tree

2 files changed

+177
-2
lines changed

2 files changed

+177
-2
lines changed

8/namespace.md

Lines changed: 167 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,11 +263,177 @@ typedef struct _zend_file_context {
263263
* __c.导入函数:__ 通过`use function`导入到FC(imports_function),补全时先查找FC(imports_function),如果没有找到则继续按照a的情况处理
264264
* __d.导入常量:__ 通过`use const`导入到FC(imports_const),不全是先查找FC(imports_const),如果没有找到则继续按照a的情况处理
265265

266-
比如:
267266
```php
268267
use aa\bb; //导入namespace
269268
use aa\bb\MY_CLASS; //导入类
270269
use function aa\bb\my_func; //导入函数
271270
use const aa\bb\MY_CONST; //导入常量
272271
```
272+
接下来看下内核的具体实现,首先看下use的编译:
273+
```c
274+
void zend_compile_use(zend_ast *ast)
275+
{
276+
zend_string *current_ns = FC(current_namespace);
277+
//use的类型
278+
uint32_t type = ast->attr;
279+
//根据类型获取存储哈希表:FC(imports)、FC(imports_function)、FC(imports_const)
280+
HashTable *current_import = zend_get_import_ht(type);
281+
...
282+
//use可以同时导入多个
283+
for (i = 0; i < list->children; ++i) {
284+
zend_ast *use_ast = list->child[i];
285+
zend_ast *old_name_ast = use_ast->child[0];
286+
zend_ast *new_name_ast = use_ast->child[1];
287+
//old_name为use后的namespace名称,new_name为as定义的别名
288+
zend_string *old_name = zend_ast_get_str(old_name_ast);
289+
zend_string *new_name, *lookup_name;
290+
291+
if (new_name_ast) {
292+
//如果有as别名则直接使用
293+
new_name = zend_string_copy(zend_ast_get_str(new_name_ast));
294+
} else {
295+
const char *unqualified_name;
296+
size_t unqualified_name_len;
297+
if (zend_get_unqualified_name(old_name, &unqualified_name, &unqualified_name_len)) {
298+
//按"\"分割,取最后一节为new_name
299+
new_name = zend_string_init(unqualified_name, unqualified_name_len, 0);
300+
} else {
301+
//名称中没有"\":use aa
302+
new_name = zend_string_copy(old_name);
303+
}
304+
}
305+
//如果是use const则大小写敏感,其它用法都转为小写
306+
if (case_sensitive) {
307+
lookup_name = zend_string_copy(new_name);
308+
} else {
309+
lookup_name = zend_string_tolower(new_name);
310+
}
311+
...
312+
if (current_ns) {
313+
//如果当前是在命名空间中则需要检查名称是否冲突
314+
...
315+
}
316+
317+
//插入FC(imports/imports_function/imports_const),key为lookup_name,value为old_name
318+
if (!zend_hash_add_ptr(current_import, lookup_name, old_name)) {
319+
...
320+
}
321+
}
322+
}
323+
```
324+
从use的编译过程可以看到,编译时的主要处理是把use导入的名称以别名或最后分节为key存储到对应的哈希表中,接下来我们看下在编译使用类、函数、常量的语句时是如何处理的。使用的语法类型比较多,比如类的使用就有new、访问静态属性、调用静态方法等,但是不管什么语句都会经历获取类名、函数名、常量名这一步,类名的补全就是在这一步完成的。
325+
326+
__(1)补全类名__
327+
328+
编译时通过zend_resolve_class_name()方法进行类名补全,如果没有任何namespace那么就返回原始的类名,比如编译`new my_class()`时,首先会把"my_class"传入该函数,如果查找FC(imports)后发现是一个use导入的类则把补全后的完整名称返回,然后再进行后续的处理。
329+
```c
330+
zend_string *zend_resolve_class_name(zend_string *name, uint32_t type)
331+
{
332+
char *compound;
333+
//"namespace\xxx\类名"这种用法表示使用当前命名空间
334+
if (type == ZEND_NAME_RELATIVE) {
335+
return zend_prefix_with_ns(name);
336+
}
337+
338+
//完全限定的形式:new \aa\bb\my_class()
339+
if (type == ZEND_NAME_FQ || ZSTR_VAL(name)[0] == '\\') {
340+
if (ZSTR_VAL(name)[0] == '\\') {
341+
name = zend_string_init(ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1, 0);
342+
} else {
343+
zend_string_addref(name);
344+
}
345+
...
346+
return name;
347+
}
348+
349+
//如果当前脚本有通过use导入namespace
350+
if (FC(imports)) {
351+
compound = memchr(ZSTR_VAL(name), '\\', ZSTR_LEN(name));
352+
if (compound) {
353+
// 1) 没有直接导入一个类的情况,用法a
354+
//名称中包括"\",比如:new aa\bb\my_class()
355+
size_t len = compound - ZSTR_VAL(name);
356+
//根据按"\"分割后的最后一节为key查找FC(imports)
357+
zend_string *import_name =
358+
zend_hash_find_ptr_lc(FC(imports), ZSTR_VAL(name), len);
359+
//如果找到了表示通过use导入了namespace
360+
if (import_name) {
361+
return zend_concat_names(
362+
ZSTR_VAL(import_name), ZSTR_LEN(import_name), ZSTR_VAL(name) + len + 1, ZSTR_LEN(name) - len - 1);
363+
}
364+
} else {
365+
// 2) 通过use导入一个类的情况,用法b
366+
//直接根据原始类名查找
367+
zend_string *import_name
368+
= zend_hash_find_ptr_lc(FC(imports), ZSTR_VAL(name), ZSTR_LEN(name));
369+
370+
if (import_name) {
371+
return zend_string_copy(import_name);
372+
}
373+
}
374+
}
375+
//没有使用use或没命中任何use导入的namespace,按照基本用法处理:如果当前在一个namespace下则解释为currentnamespace\my_class
376+
return zend_prefix_with_ns(name);
377+
}
378+
```
379+
此方法除了类的名称后还有一个type参数,这个参数是解析语法是根据使用方式确定的,共有三种类型:
380+
* __ZEND_NAME_NOT_FQ:__ 非限定名称,也就是普通的类名,没有加namespace,比如:new my_class()
381+
* __ZEND_NAME_RELATIVE:__ 相对名称,强制按照当前所属命名空间解析,使用时通过在类前加"namespace\xx",比如:new namespace\my_class(),如果当前是全局空间则等价于:new my_class,如果当前命名空间为currentnamespace,则解析为"currentnamespace\my_class"
382+
* __ZEND_NAME_FQ:__ 完全限定名称,即以"\"开头的
383+
384+
__(2)补全函数名、常量名__
385+
386+
函数与常量名称的补全操作是相同的:
387+
```c
388+
//补全函数名称
389+
zend_string *zend_resolve_function_name(zend_string *name, uint32_t type, zend_bool *is_fully_qualified)
390+
{
391+
return zend_resolve_non_class_name(
392+
name, type, is_fully_qualified, 0, FC(imports_function));
393+
}
394+
//补全常量名称
395+
zend_string *zend_resolve_const_name(zend_string *name, uint32_t type, zend_bool *is_fully_qualified)
396+
return zend_resolve_non_class_name(
397+
name, type, is_fully_qualified, 1, FC(imports_const));
398+
}
399+
```
400+
可以看到函数与常量最终调用同一方法处理,不同点在于传入了各自的存储哈希表:
401+
```c
402+
zend_string *zend_resolve_non_class_name(
403+
zend_string *name, uint32_t type, zend_bool *is_fully_qualified,
404+
zend_bool case_sensitive, HashTable *current_import_sub
405+
) {
406+
char *compound;
407+
*is_fully_qualified = 0;
408+
//完整名称,直接返回,不需要补全
409+
if (ZSTR_VAL(name)[0] == '\\') {
410+
*is_fully_qualified = 1;
411+
return zend_string_init(ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1, 0);
412+
}
413+
//与类的用法相同
414+
if (type == ZEND_NAME_RELATIVE) {
415+
*is_fully_qualified = 1;
416+
return zend_prefix_with_ns(name);
417+
}
418+
//current_import_sub如果是函数则为FC(imports_function),否则为FC(imports_const)
419+
if (current_import_sub) {
420+
//查找FC(imports_function)或FC(imports_const)
421+
...
422+
}
423+
//查找FC(imports)
424+
compound = memchr(ZSTR_VAL(name), '\\', ZSTR_LEN(name));
425+
...
426+
427+
return zend_prefix_with_ns(name);
428+
}
429+
```
430+
可以看到,函数与常量的的补全逻辑只是优先用原始名称去FC(imports_function)或FC(imports_const)查找,如果没有找到再去FC(imports)中匹配。如果我们这样导入了一个函数:`use aa\bb\my_func;`,编译`my_func()`会在FC(imports_function)中根据"my_func"找到"aa\bb\my_func",从而使用完整的这个名称。
431+
432+
### 8.3.3 动态使用
433+
前面介绍的这些命名空间的使用都是名称为CONST类型的情况,所有的处理都是在编译环节完成的,PHP是动态语言,能否动态使用命名空间呢?举个例子:
434+
```php
435+
$class_name = "\aa\bb\my_class";
436+
$obj = new $class_name;
437+
```
438+
如果类似这样的用法只能只用完全限定名称,也就是按照实际存储的名称使用,无法进行自动名称补全。
273439

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,16 @@
9494
* 7.11 经典扩展解析
9595
* 7.8.1 Yaf
9696
* 7.8.2 Redis
97-
97+
* 第8章 命名空间
98+
* [8.1 概述](8/namespace.md)
99+
* [8.2 命名空间的定义](8/namespace.md)
100+
* [8.2.1 定义语法](8/namespace.md)
101+
* [8.2.2 内部实现](8/namespace.md)
102+
* [8.3 命名空间的使用](8/namespace.md)
103+
* [8.3.1 基本用法](8/namespace.md)
104+
* [8.3.2 use导入](8/namespace.md)
105+
* [8.3.3 动态用法](8/namespace.md)
106+
98107
## 附录
99108
* [附录1:break/continue按标签中断语法实现](try/break.md)
100109

0 commit comments

Comments
 (0)