类的内部存储结构:
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) };
1)类分为5种
a. 常规类:type=0
b. 抽象类:type=ZEND_ACC_EXPLICIT_ABSTRACT_CLASS
c. final类:type=ZEND_ACC_FINAL_CLASS
d. 没有加abstract关键字的抽象类:type=ZEND_ACC_IMPLICIT_ABSTRACT_CLASS
e. 接口:type=ZEND_ACC_INTERFACE
定义类时调用了zend_do_begin_class_declaration和zend_do_end_class_declaration函数。
“begin”函数用来处理类名,类的类别和父类,对传入的类名作一个转化,统一成小写,这也是为什么类名不区分大小的原因。
“end”函数用来处理接口和类的中间代码。这两个函数在Zend/zend_complie.c文件中可以找到其实现。
2)成员变量
成员变量在编译时已经注册到了类的结构中。
调用顺序为: [zend_do_begin_class_declaration] --> [zend_initialize_class_data] --> [zend_hash_init_ex]
常规的成员变量最后都会注册到类的 default_properties 字段。
3)静态变量
类本身的静态变量存放在类结构的 default_static_members 字段中。
和前面的成员变量一样,在类声明时成员方法也通过调用zend_initialize_class_data方法,初始化了整个方法列表所在的HashTable。
静态变量更新流程图:
4)成员方法
以HashTable的数据结构存储了多个zend_function结构体,放在 function_table 字段中。
前面的成员变量一样,在类声明时成员方法也通过调用zend_initialize_class_data方法,初始化了整个方法列表所在的HashTable。
5)静态成员方法
与静态成员变量不同,静态成员方法与成员方法都存储在类结构的 function_table 字段。
类的静态成员方法可以通过类名直接访问。
6)方法(Function)与函数(Method)的差别
a. 编译后“挂载”的位置不同
b. 调用方式的实现
7)魔术方法
与普通方法一样, 只不过这些类不是存储在类的函数表, 而是直接存储在类结构体中。
由ZendVM自动分情境进行调用。
struct _zend_class_entry { ... //构造方法 __construct union _zend_function *constructor; //析构方法 __destruct union _zend_function *destructor; //克隆方法 __clone 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 *__callstatic; union _zend_function *__tostring; //序列化 union _zend_function *serialize_func; //反序列化 union _zend_function *unserialize_func; ... }
a. __construct:调用入口是new关键字对应的ZEND_NEW_SPEC_HANDLER函数。
b. __destruct:对象被显示销毁或者脚本关闭时,一般被用于释放占用的资源。
c. __call:在对对象不存在的方法进行调用时自动执行。
d. __callStatic:在对对象不存在的静态方法进行调用时自动执行。
8)self、parent和static
self与parent不是关键字,而static是关键字。
self,parent和static的指向会在执行时进行获取。
在 zend_fetch_class 执行函数中:
1. self:返回EG(scope)类
2. parent:返回EG(scope)->parent类
3. static:返回EG(called_scope)类。后面的延迟绑定就是“static”的操作。
9)延迟绑定
“延迟绑定”的意思是说,static::不再被解析为定义当前方法所在的类,而是在实际运行时计算的。
延迟绑定的实现关键在于static关键字。
class ParentBase { static $property = 'Parent Value'; public static function render() { //return self::$property; return static::$property; } } class Descendant extends ParentBase { static $property = 'Descendant Value'; } echo Descendant::render(); //Descendant Value
面向对象的三大特性(封装、继承、多态),PHP提供了public、protected及private三个层次访问控制。
语法解析中对应的标记如下:
1. private:ZEND_ACC_PUBLIC 0x100
2. protected:ZEND_ACC_PROTECTED 0x200
3. public:ZEND_ACC_PRIVATE 0x400
在语法解析过程中,访问控制已经存储在编译节点中,在编译具体的类成员时会传递给相关的结构。此变量会作为一个参数传递给生成中间代码的函数。
如在解析成员方法时,PHP内核是通过调用 zend_do_begin_function_declaration 函数实现,此函数的第五个参数表示访问控制。
if (fbc->op_array.fn_flags & ZEND_ACC_PUBLIC) { //公有方法,可以访问 } else if (fbc->op_array.fn_flags & ZEND_ACC_PRIVATE) { // 私有方法,报错 } else if ((fbc->common.fn_flags & ZEND_ACC_PROTECTED)) { // 保护方法,报错 }
1)继承
通过对extends关键字的词法分析和语法分析,在Zend/zend_complie.c文件中 找到继承实现的编译函数zend_do_inheritance()。
其调用顺序如下: [zend_do_early_binding] --> [do_bind_inherited_class()] --> [zend_do_inheritance()]。
继承的过程如下:
a. 以类结构为中心,当继承发生时,程序会先处理所有的接口。
b. 在接口继承后,程序会合并类的成员变量、属性、常量、函数等,这些都是HashTable的merge操作。
c. 除了常规的函数合并后,还有魔法方法的合并。
d. 在PHP中private这个成员是会被继承下来,只是无法访问。
2)多态
多态即多种形态,相同方法调用实现不同的实现方式。
interface Animal { public function run(); } class Dog implements Animal { public function run() { echo 'dog run'; } } class Cat implements Animal{ public function run() { echo 'cat run'; } }
3)接口
PHP内核对类和接口一视同仁,它们的内部结构一样,量都是 zend_class_entry 类型。
a. 类的继承由于是一对一的关系,每个类都有一个parent字段。
b. 接口实现是一个一对多的关系,每个类都会有一个二维指针存放接口的列表 **interfaces ,还有一个存储接口数的字段 num_interfaces 。
c. 接口在编译时调用zend_do_implement_interface函数, 此函数会合并接口中的常量列表和方法列表操作,这就是接口中不能有变量却可以有常量的实现原因。
d. 在接口继承的过程中有对当前类的接口中是否存在同样接口的判断操作,如果已经存在了同样的接口,则此接口继承将不会执行。
4)抽象类
在PHP中,抽象类是被abstract关键字修饰的类,或者类没有被声明为abstract,但是在类中存在抽象成员的类。
在结构体zend_class_entry.ce_flags中体现了区别:
二者对应的值为ZEND_ACC_EXPLICIT_ABSTRACT_CLASS和ZEND_ACC_IMPLICIT_ABSTRACT_CLASS。
类的具体化就是对象,我们常常也说对象是类的实例。
对象拥有方法,对象间的通信是通过方法调用,以一种消息传递的方式进行。
我们常说的面向对象编程(OOP)使得对象具有交互能力的主要模型就是消息传递模型。
1)对象的结构
对象的结构体如下:
typedef struct _zend_object_value { zend_object_handle handle; // unsigned int类型,EG(objects_store).object_buckets的索引 zend_object_handlers *handlers; } zend_object_value;
PHP内核会将所有的对象存放在一个对象列表容器中,这个列表容器是保存在EG(objects_store)里的一个全局变量。
上面的handle字段就是这个列表中object_buckets的索引。
当我们需要在PHP中存储对象的时候, PHP内核会根据handle索引从对象列表中获取相对应的对象。而获取的对象有其独立的结构:
typedef struct _zend_object { zend_class_entry *ce; HashTable *properties; HashTable *guards; /* protects from __get/__set ... recursion */ } zend_object;
ce是存储该对象的类结构,properties是一个HashTable,用来存放对象的属性。
2)对象的创建
在PHP代码中,对象的创建是通过关键字 new 进行的。 一个new操作最终会产生三个opcode:
a. 根据类名获取存储类的变量
b. 初始化对象
1. 首先会判断对象所对应的类是否为可实例化的类
2. 如果一切正常,程序会给需要创建的对象存放的ZVAL容器分配内存
3. 在设置了类型之后,程序会执行zend_object类型的对象的初始化工作,此时调用的函数是zend_objects_new
4. zend_objects_new函数会初始化对象自身的相关信息,包括对象归属于的类,对象实体的存储索引,对象的相关处理函数
5. 在将对象放入对象池,返回对象的存放索引后,程序设置对象的处理函数为标准对象处理函数:std_object_handlers
c. 调用构造函数
3)对象池
将PHP内核在运行中存储所有对象的列表称之为对象池,即EG(objects_store)。
这个对象池的作用是存储PHP中间代码运行阶段所有生成的对象。
4)成员变量
对象的成员变量存储在 properties 参数中。
通过__set来设置变量,通过__get来获取变量。
参考资料:
http://php-internals.com/book/?p=chapt05/05-01-class-struct 类的结构和实现
http://php-internals.com/book/?p=chapt05/05-02-class-member-variables-and-methods 类的成员变量及方法
http://php-internals.com/book/?p=chapt05/05-04-class-inherit-abstract 类的继承,多态及抽象类
http://php-internals.com/book/?p=chapt05/05-05-class-magic-methods-latebinding 魔术方法,延迟绑定及静态成员
http://php-internals.com/book/?p=chapt05/05-07-class-object 对象
http://php-internals.com/book/?p=chapt05/05-06-class-reserved-and-special-classes PHP保留类及特殊类