· 作者: Laruence( )
· 本文地址: http://www.laruence.com/2008/08/24/427.html
· 转载请注明出处
今天guoxiaod提出了一个问题,如下:
1. <?php
2. class a extends b {
3. };
4. class b extends c{
5. };
6. class c{
7. };
8. ?>
9.
会导致fatal error:
1. PHP Fatal error: Class 'b' not found in /home/xinchen/1.php on line 2
2. Fatal error: Class 'b' not found in /home/xinchen/1.phpon line 2
分析这个问题,是运行阶段出错,经过分析PHP的编译,执行过程,得出如下的parsing顺序…
1. start:
2. top_statement_list
3. ;
4.
5. top_statement_list:
6. top_statement_list
7. .... //有省略
8. ;
9.
10. top_statement:
11. .... //有省略
12. | class_declaration_statement
13. .... //有省略
14. ;
15.
16. class_declaration_statement:
17. unticked_class_declaration_statement
18. ;
19.
20. unticked_class_declaration_statement:
21. class_entry_typeT_STRING extends_from
22. .... //有省略
23. ;
24.
25. class_entry_type:
26. T_CLASS
27. .... //有省略
28. ;
29.
30. extends_from:
31. /* empty*/
32. | T_EXTENDS fully_qualified_class_name
33. .... //有省略
34. ;
35. fully_qualified_class_name:
36. T_STRING{ zend_do_fetch_class(&$$, &$1 TSRMLS_CC); }
37. ;
1. zend_do_fetch_class 会设置opcode = ZEND_FETCH_CLASS
从这个过程我们可以发现,这个应该是PHP5的bug,对于fully_qualified_class_name,如果fully_qualified_class_name也是继承来自一个类,那么就会出错,因为fully_qualified_class_name只是简单的去fetch_class,而如果这个时候,这个类还没有被填入到class_table就会出错。也就是说,需要有个机制,来保证父class首先被处理。
以下是我分析源码后的结论:
对于a,因为是个派生类,在编译阶段,当遇到它的定义的时候,会:
1. zend_do_begin_class_declaration
在这个函数中,会调用:
1. build_runtime_defined_function_key(&opline->op1.u.constant,lcname, name_len TSRMLS_CC);
来产生一个:
1. sprintf(result->value.str.val, "%c%s%s%s",'\0', name, filename, char_pos_buf);
的字符串,来做为一个编译器的classname存入class_table:
1. zend_hash_update(CG(class_table), opline->op1.u.constant.value.str.val, opline->op1.u.constant.value.str.len, &new_class_entry, sizeof(zend_class_entry *), NULL);
最后在吸收top_statement的时候,会有一次类的生成(填入class_table);
1. top_statement:
2. statement
3. ...
4. | class_declaration_statement { zend_do_early_binding(TSRMLS_C); }
5. ...
6. ...
7. ;
在zend_do_early_binding的时候:
1. void zend_do_early_binding(TSRMLS_D){
2. ...
3. ...
4. switch (opline->opcode) {
5. case ZEND_DECLARE_FUNCTION:
6. if (do_bind_function(opline, CG(function_table), 1) == FAILURE) {
7. return;
8. }
9. table= CG(function_table);
10. break;
11. case ZEND_DECLARE_CLASS:
12. case ZEND_DECLARE_INHERITED_CLASS:
13. is_abstract_class= 1;
14. /* break missing intentionally */
15. case ZEND_VERIFY_ABSTRACT_CLASS: {
16. zend_op*verify_abstract_class_op = opline;
17.
18. if (!is_abstract_class) {
19. opline--;
20. }
21. if (opline->opcode == ZEND_DECLARE_CLASS) {
22. if (do_bind_class(opline, CG(class_table), 1 TSRMLS_CC) == NULL) {
23. return;
24. }
25. } elseif (opline->opcode == ZEND_DECLARE_INHERITED_CLASS) {
26. zval*parent_name = &(opline-1)->op2.u.constant;
27. zend_class_entry**pce;
28.
29. if (zend_lookup_class(Z_STRVAL_P(parent_name), Z_STRLEN_P(parent_name), &pce TSR
30. MLS_CC) == FAILURE) {
31. return;
32. }
33. if (do_bind_inherited_class(opline, CG(class_table), *pce, 1 TSRMLS_CC) == NULL)
34. {
35. return;
36. }
37. /* clear unnecessary ZEND_FETCH_CLASS opcode*/
38. }
看到了吧,如果找不到父类,就直接返回了,也就是说,派生类在编译期如果找不到父类,就不会被真正初始化,而是推迟到执行期。会分配一个opcode为ZEND_DECLARE_INHERITED_CLASS的opline,用来在运行期真正生成定义的类:
1. ZEND_API zend_class_entry *do_bind_inherited_class(zend_op *opline, HashTable *class_table, zend_class_entry *parent_ce, zend_bool compile_time TSRMLS_DC)
2. {
3. .......
4. //hash_merg子类和父类的属性、方法
5. if (zend_hash_add(class_table, opline->op2.u.constant.value.str.val, opline->op2.u.constant.value.str.len+1, pce, sizeof(zend_class_entry *), NULL)==FAILURE)
6. .....
7. }
这个时候问题就来了:
因为我们的b也是一个派生类,所以在执行a的do_bind_inherited_class时候,对于b,他也需要做一个ZEND_DECLARE_INHERITED_CLASS,也就是说,此时的class_table中是没有b的。
这也就解释了,如果最基类c,定义在前的时候,就不会出错。
恩,这个应该是PHP5的一个Bug。