@@ -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 
268267use aa\bb; //导入namespace
269268use aa\bb\MY_CLASS; //导入类
270269use function aa\bb\my_func; //导入函数
271270use 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
0 commit comments