深入理解PHP内核(十三)类的结构和实现
原文链接:http://www.orlion.ga/1117/
先看一下类的结构:
struct _zend_class_entry { char type; // 类型:ZEND_INTERNAL_CLASS / ZEND_USER_CLASS char *name;// 类名称 zend_uint name_length; // 即sizeof(name) - 1 structͺ_zend_class_entry *parent; // 继承的父类 intͺrefcount; // 引用数 zend_bool constants_updated; zend_uint ce_flags; // ZEND_ACC_IMPLICIT_ABSTRACT_CLASS: 类存在abstract方法 // ZEND_ACC_EXPLICIT_ABSTRACT_CLASS: 在类名称前加abstract关键字 // ZEND_ACC_FINAL_CLASS // ZEND_ACC_INTERFACE HashTable function_table; // 方法 HashTable default_properties; // 默认属性 HashTable properties_info; // 属性信息 HashTable default_static_members;// 类本身所具有的静态变量 HashTable *static_members; // type == ZEND_USER_CLASS时,取&default_static_members; // type == ZEND_INTERAL_CLASS时,设NULL HashTable constants_table; // 常量 struct _zend_function_entry *builtin_functions;// 方法定义入口 union _zend_function *constructor; union _zend_function *destructor; union _zend_function *clone; /* 魔术方法 */ union _zend_function *__get; union _zend_function *__set; union _zend_function *__unset; union _zend_function *__isset; union _zend_function *__call; union _zend_function *__tostring; union _zend_function *serialize_func; union _zend_function *unserialize_func; zend_class_iterator_funcs iterator_funcs;// 迭代 /* 类句柄 */ zend_object_value (*create_object)(zend_class_entry *class_type TSRMLS_DC); zend_object_iterator *(*get_iterator)(zend_class_entry *ce, zval *object, intby_ref TSRMLS_DC); /* 类声明的接口 */ int(*interface_gets_implemented)(zend_class_entry *iface, zend_class_entry *class_type TSRMLS_DC); /* 序列化回调函数指针 */ int(*serialize)(zval *object unsignedchar**buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC); int(*unserialize)(zval **object, zend_class_entry *ce, constunsignedchar*buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC); zend_class_entry **interfaces; // 类实现的接口 zend_uint num_interfaces; // 类实现的接口数 char *filename; // 类的存放文件地址 绝对地址 zend_uint line_start; // 类定义的开始行 zend_uint line_end; // 类定义的结束行 char *doc_comment; zend_uint doc_comment_len; struct _zend_module_entry *module; // 类所在的模块入口EG(current_module) };
类的结构中,type有两种类型,数字标记为1和2。分别为宏定义,分别是内置的类和用户自定义的类
#define ZEND_INTERNAL_CLASS 1 #define ZEND_USER_CLASS 2
父类和接口都是存在struct _zend_class_entry中,即接口也是已类的形式存在,类的常规成员方法放在函数结构体哈希表中,而魔术方法单独存放。如在类定义中的 union _zend_function *constructor;定义就是类的构造魔术方法,它是以函数的形式存在类结构中,初始化时这些魔术方法都会被设置为NULL。
类的实现
类的定义是以class关键字开始,在Zend/zend_language_scanner.l文件中,找到class对应的token为T_CLASS。根据此token,在Zend/zend_language_parser.y文件中,找到编译时调用的函数:
unticked_class_declaration_statement: class_entry_type T_STRING extends_from { zend_do_begin_class_declaration(&$1, &$2, &$3 TSRMLS_CC); } implements_list '{' class_statement_list '}' { zend_do_end_class_declaration(&$1, &$2 TSRMLS_CC); } | interface_entry T_STRING { zend_do_begin_class_declaration(&$1, &$2, NULL TSRMLS_CC); } interface_extends_list '{' class_statement_list '}' { zend_do_end_class_declaration(&$1, &$2 TSRMLS_CC); } ; class_entry_type: T_CLASS { $$.u.opline_num = CG(zend_lineno); $$.u.EA.type = 0; } | T_ABSTRACT T_CLASS { $$.u.opline_num = CG(zend_lineno); $$.u.EA.type = ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; } | T_FINAL T_CLASS { $$.u.opline_num = CG(zend_lineno); $$.u.EA.type = ZEND_ACC_FINAL_CLASS; } ;
上面的class_entry_type语法说明在语法分析阶段将类分为三种类型:常规类(T_CLASS),抽象类(T_ABSTRACT T_CLASS)和final类(T_FINAL T_CLASS)。他们分别对应的类型在内核中为:
-
常规类(T_CLASS)对应的type=0
-
抽象类(T_ABSTRACT T_CLASS)对应type=ZEND_ACC_EXPLICIT_ABSTRACT_CLASS
-
final类(T_FINAL T_CLASS)对应type=ZEND_ACC_FINAL_CLASS
除了上面三种类型外,类还有另外两种类型没有加abstract关键字的抽象类和接口:
-
没有加abstract关键字的抽象类,它对应的type=ZEND_ACC_IMPLICIT_ABSTRACT_CLASS。由于没在class前面没有abstract关键字,在语法分析时并没有分析出来这是一个抽象类,但是由于类中没有抽象方法,在函数注册时判断成员函数是抽象方法或继承类中的成员方法是抽象方法,会将这个类设置为此种抽象类型。
-
接口,其type=ZEND_ACC_INTERFACE。接口类型的区分是在interface关键字解析时设置。
这五种类型在Zend/zend_complie.h文件中定义如下:
#define ZEND_ACC_IMPLICIT_ABSTRACT_CLASS 0x10 #define ZEND_ACC_EXPLICIT_ABSTRACT_CLASS 0x20 #define ZEND_ACC_FINAL_CLASS 0x40 #define ZEND_ACC_INTERFACE 0x80
常规类为0。语法解析完后就知道一个类是抽象类还是final类、普通的类、接口。定义类时调用了zend_do_begin_class_declaration和zend_do_end_class_declaration函数,从这两个函数传入的参数,zend_do_begin_class_declaration函数用来处理类名,类的类别和父类,zend_do_end_class_declaration函数用来处理接口和类的中间代码,这两个函数在Zend/zend_complie.c文件中可以找到其实现。
在zend_do_begin_class_declaration中,首先会对传入的类名做一个转化,统一成小写(这也是类名不区分大小写的原因)。
类名重复(如定义了两个Person类)错误的判断过程在中间代码生成时,关于类名的判断是通过T_STRING token,在语法解析时做的判断,但是这只能识别出类名是一个字符串。假如类名为一些关键字如声明class self会报错,这个错误的判断定义在zend_do_begin_class_declaration函数,与self关键字一样,还有parent,static两个关键字的判断在同一个地方。当这个函数执行完后,我们会得到类声明生成的中间代码为ZEND_DECLARE_CLASS。如果我们声明内部类的话,生成的中间代码为:ZEND_DECLARE_INHERITED_CLASS。
根据生成的中间代码,我们在Zend/zend_vm_execute.h文件中找到其对应的执行函数ZEND_DECLARE_CLASS_SPEC_HANDLER。这个函数通过调用do_bind_class函数将此类加入到EG(class_table)。在添加到列表的同时,也判断该类是否存在,如果存在,则添加失败,报错,只是这个判断在编译开启时是不会生效的。
类相关的各个结构均保存在struct _zend_class_entry结构体中,这些具体的类别在语法分析过程中进行区分。识别出类的类别,类的类名等,并将识别出来的结果存放到类的结构中。