【PHP7源码学习】2019-04-03 PHP类与对象

baiyanphp

所有视频:https://segmentfault.com/a/11...segmentfault

类的存储

  • 谈到PHP中的类,咱们知道,类是对象的抽象,是全部经过它new出来对象的模板,它是编译阶段的产物。一个类被抽象出来,它自己有本身的属性、方法等等要素。若是让咱们本身去用C语言实现一个类的存储结构,咱们如何设计?
  • 类的几大要素:类常量、普通属性、静态属性、方法
  • 类做用域:全部对象之间共享,如类常量、静态属性、方法
  • 对象做用域:全部对象之间独享,如普通属性、动态属性
  • 下面咱们逐个来看究竟它们是被如何存储的:

类常量的存储

  • 在PHP7中,使用一个叫作zend_class_entry的结构体来存储类的相关数据。
  • 类常量不能被修改,属于类做用域,以const关键字标识,全部对象共享一份类常量。
  • 首先咱们举一个PHP类常量的例子:
class A{
    const PI = 3.14;
}
  • 这里的PI就是一个类常量。常量名为PI,常量值为3.14。咱们能够用两种方式来访问它:
  • 类外:A::PI
  • 类内:self::PI
  • 那么咱们看一下常量的存储结构:
struct _zend_class_entry {
    ...
    HashTable constants_table; //常量哈希表,key为常量名,value为常量值
    ...
};
  • 在PHP7中,类是以一个zend_class_entry结构体来存储的。其中这个constants_table字段,就是用来存储类常量的。咱们知道,常量是属于类做用域的,而不是对象做用域,因此它的值被直接放在类结构体中。它是一个hashtable,其中key为常量名,value为常量值。当访问某个常量值的时候,咱们能够直接根据常量的名字做为key,到hashtable中查找对应的常量值便可,这里仍是很好理解的。

普通属性的存储

  • 普通属性属于对象做用域,每一个对象的属性值能够不一样,由于咱们如今讲的是类,因此咱们在类做用域下讲解一下和普通属性相关的数据在类结构中,究竟在哪里有所体现。
  • 举一个PHP普通属性的例子:
class A{
    public $name = 'jby';
}
  • 这里name就是属性名,它有一个初始化值为jby,也有两种访问方式:
  • 类内部:$this->name
  • 类外部:对象->name
  • 下面看一下在类结构zend_class_entry中,与普通属性存储相关的字段:
struct _zend_class_entry {
    ...
    int default_properties_count; //普通属性的数量总和
    ...
    zval *default_properties_table; //存放普通属性的初始化值的数组
    ...
    HashTable properties_info; //存储对象属性的信息哈希表,key为属性名,value为zend_property_info结构体
    ... 
}
  • int default_properties_count字段存储一个类中全部普通属性的数量之和
  • 咱们知道,因为普通属性是对象做用域,因此每个对象下的普通属性值是不一样的,因此针对不一样对象的属性值,须要放在具体不一样对象的结构中去存储。可是,因为PHP容许普通属性具备初始化值(如上例的jby),而这个初始化值在全部对象实例中共享,故初始化值能够放在类做用域中进行存储。因此初始化的值(如上例的jby)能够直接存储在类结构体下的zval *default_properties_table这个zval数组中,default_properties_table里的元素的zend_value中的str指针指向zend_string,其值为jby。
  • 而后咱们看具体每一个对象中属性的存储。因为普通属性有访问权限(public/protected/private)等额外信息须要存储,因此在类做用域内,存储普通属性的信息须要一个结构体,并且是一个普通属性就要对应一个结构体来存储它的信息。
  • 在类结构zend_class_entry中,咱们使用HashTable properties_info这个字段来存储普通属性的信息,而这个字段是一个hashtable,它的key为属性名,value为一个结构体,它就是用来存储每个普通属性的信息的,叫作zend_property_info。每个属性,就会对应一个zend_property_info结构:
typedef struct _zend_property_info {
    uint32_t offset; //表示普通属性的内存偏移值或静态属性的数组索引
    uint32_t flags;  //属性掩码,如public、private、protected及是否为静态属性
    zend_string *name; //属性名
    zend_string *doc_comment; //文档注释信息
    zend_class_entry *ce; //所属类
} zend_property_info;

//flags标识位
#define ZEND_ACC_PUBLIC     0x100
#define ZEND_ACC_PROTECTED  0x200
#define ZEND_ACC_PRIVATE    0x400
#define ZEND_ACC_STATIC      0x01
  • 咱们看这个存储普通属性信息的结构体。下面的属性名等字段咱们很容易理解,那么重点则是这个offset字段。因为类做用域是不能肯定每一个对象中普通属性的值的(不一样对象属性值不一样),因此普通属性的值会在对象存储结构zend_object中以数组的形式存储(实际上是一个柔性数组,后面会讲到)。它的字面意义是偏移量,那么这个偏移量是相对于谁的偏移量呢?答案就是相对于上述的存储值的柔性数组的偏移量,这个偏移量是以一个zval大小(16)递增的(下面讲到对象结构的时候会具体讲)

静态属性的存储

  • 静态属性也属于类做用域,以static关键字标识,全部对象共享类中的静态属性。因此在类结构zend_class_entry中,就能够直接将静态属性的值存到这个类结构中,静态属性的使用示例以下:
class A{
    static $instance = null;
}
  • 访问静态属性也有两种方式:
  • 类内部:self::$instance
  • 类外部:A::$instance
  • 静态属性在全部对象中共享,因此在类做用域中,能够直接存储它的值:
struct _zend_class_entry {
    ...
    int default_static_members_count;    //静态属性数量总和
    ...
    zval *default_static_members_table;  //存放静态属性初始化值的数组
    zval *static_members_table; //存放静态属性值的数组
    ...
    HashTable properties_info; //存储对象属性的信息哈希表,key为属性名,value为zend_property_info结构体
    ...
}
  • int default_static_members_count字段存储一个类中全部静态属性的数量之和
  • default_static_members_table用来存放静态属性的初始化值,这一点和普通属性初始化值的存放是相同思想,再也不赘述
  • static_members_table用来直接存放静态属性的值
  • HashTable properties_info一样也是一个key为属性名,value为zend_porperty_info结构体的hashtable,里面一样存放着offset,而这个offset表明每个静态属性在static_members_table和default_static_members_table这两个存放值的数组中的索引。这样,咱们能够快速地根据当前的静态属性名,根据静态属性名这个key,在hashtable中查找到zend_property_info结构体中的offset字段,根据这个偏移量,进而去对应的数组单元中,也就是static_members_table或default_static_members_table数组中,找到当前静态属性名对应的值,这样就快速地完成了一次静态属性的访问。

方法的存储

  • 因为方法也属于类做用域,全部对象共享相同的方法体。因此在类结构中,就可直接以一个hashtable存储方法。key为方法名称,value为具体的zend_function:
struct _zend_class_entry {
    ...
    HashTable function_table;  //成员方法哈希表
    ...
}

其余

  • 一个类,可能它是一个继承了父类的一个子类,也多是是一个抽象类或接口、甚至是trait,因此须要一些字段来存储这些分类的信息。除此以外,还有类自己的构造函数、析构函数等等。那么这些信息,咱们要如何去表示呢?如今咱们看一下这个完整的zend_class_entry类结构:
struct _zend_class_entry {
    char type;          //类的类型:内部类ZEND_INTERNAL_CLASS(1)、用户自定义类ZEND_USER_CLASS(2)
    zend_string *name;  //类名
    struct _zend_class_entry *parent; //父类指针
    int refcount; //引用计数
    uint32_t ce_flags;  //类掩码,如普通类、抽象类、接口等等

    int default_properties_count;        //普通属性的数量总和
    int default_static_members_count;    //静态属性数量总和
    zval *default_properties_table;      //存放普通属性初始化值的数组
    zval *default_static_members_table;  //存放静态属性初始化值的数组
    zval *static_members_table; //存放静态属性值的数组
    HashTable function_table;  //成员方法哈希表
    HashTable properties_info; //存储对象属性的信息哈希表,key为属性名,value为zend_property_info结构体
    HashTable constants_table; //常量哈希表,key为常量名,value为常量值

    //构造函数、析构函数以及魔术方法的指针
    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 *__callstatic;
    union _zend_function *__tostring;
    union _zend_function *__debugInfo;
    union _zend_function *serialize_func;
    union _zend_function *unserialize_func;

    zend_class_iterator_funcs iterator_funcs;

    //自定义的钩子函数,一般是定义内部类时使用,能够灵活的进行一些个性化的操做
    //用户自定义类不会用到,暂时忽略便可
    zend_object* (*create_object)(zend_class_entry *class_type);
    zend_object_iterator *(*get_iterator)(zend_class_entry *ce, zval *object, int by_ref);
    int (*interface_gets_implemented)(zend_class_entry *iface, zend_class_entry *class_type); /* a class implements this interface */
    union _zend_function *(*get_static_method)(zend_class_entry *ce, zend_string* method);

    /* serializer callbacks */
    int (*serialize)(zval *object, unsigned char **buffer, size_t *buf_len, zend_serialize_data *data);
    int (*unserialize)(zval *object, zend_class_entry *ce, const unsigned char *buf, size_t buf_len, zend_unserialize_data *data);

    uint32_t num_interfaces; //实现的接口数量总和
    uint32_t num_traits; //使用的trait数量总和
    zend_class_entry **interfaces; //实现的接口,能够理解为它指向一个一维数组,一维数组里所有存放的都是类结构的指针,指向它所实现的接口类

    zend_class_entry **traits; //所使用的trait,理解方法同上
    zend_trait_alias **trait_aliases; //trait别名,解决多个trait中方法重名冲突的问题
    zend_trait_precedence **trait_precedences;

    union {
        struct {
            zend_string *filename;
            uint32_t line_start;
            uint32_t line_end;
            zend_string *doc_comment;
        } user;
        struct {
            const struct _zend_function_entry *builtin_functions;
            struct _zend_module_entry *module; //所属扩展
        } internal;
    } info;
}

对象的存储

  • 如今咱们再谈对象。咱们知道,对象是类的具体实现,是运行阶段的产物。其普通属性是每一个对象独享的,因此,在分析对象中,咱们要尤为注重每一个对象独特的普通属性值是如何存储的。因为以前在讲类存储的时候已经有了铺垫,还记得以前说的zend_property_info中的offset偏移量吗,咱们带着这个知识点,直接看对象的存储结构。在PHP7中,使用一个叫作zend_object的结构体来存储对象相关的数据:
struct _zend_object {
    zend_refcounted_h gc; //内部存有引用计数
    uint32_t          handle; 
    zend_class_entry *ce; //所属的类
    const zend_object_handlers *handlers; 
    HashTable        *properties; //存储动态属性值
    zval              properties_table[1]; //柔性数组,每个单元都是zval类型,用来存储普通属性值,offset就是相对于当前字段首地址的偏移量
};

普通属性的存储

  • 咱们知道,一个对象,就对应一个zend_object结构。那么最重要的字段就是zval properties_table[1]字段了。它是一个柔性数组,放到结构体的末尾,能够存储变长大小的数据,且与结构体内存空间牢牢相连(柔性数组请看这一系列的前几篇文章有详细讲解)。
  • 在建立一个新对象的时候,在类做用域中存储的普通属性的初始化值,都会拷贝到对象结构中的柔性数组中
  • 那么如今,以前讲过的类结构中property_info哈希表中的字段的value值zend_property_info中的offset偏移量字段就要派上用场了。想一下,若是让咱们访问某个对象的普通属性的值,应该如何访问:
- 经过指针ce找到当前对象对应的类结构zend_class_entry
 - 取出当前类结构中的Hashtable property_info字段,这个字段是一个哈希表,存有属性的信息。
 - 将要查找的属性名做为key,到哈希表中找到对应的value,即zend_property_info结构体,并取出结构体中的offset字段
 - 到当前对象zend_object结构体中,经过内存地址计算(柔性数组的起始地址+offset)就能够获得所要访问的当前对象的某个普通属性的值了
  • 那么咱们看一下其余几个字段的做用:
  • handle:一次request期间对象的编号,每一个对象都有一个惟一的编号,与建立前后顺序有关,主要在垃圾回收时使用
  • handlers:保存的对象相关操做的一些函数指针,好比属性的读写、方法的获取、对象的销毁/克隆等等,这些操做接口都有默认的函数,这里存储了这些默认函数的指针:
struct _zend_object_handlers {
    int                                     offset;
    zend_object_free_obj_t                  free_obj; //释放对象
    zend_object_dtor_obj_t                  dtor_obj; //销毁对象
    zend_object_clone_obj_t                 clone_obj;//复制对象
    
    zend_object_read_property_t             read_property; //读取成员属性
    zend_object_write_property_t            write_property;//修改为员属性
    ...
}

//处理对象的handler
ZEND_API zend_object_handlers std_object_handlers = {
    0,
    zend_object_std_dtor,                   /* free_obj */
    zend_objects_destroy_object,            /* dtor_obj */
    zend_objects_clone_obj,                 /* clone_obj */
    zend_std_read_property,                 /* read_property */
    zend_std_write_property,                /* write_property */
    zend_std_read_dimension,                /* read_dimension */
    zend_std_write_dimension,               /* write_dimension */
    zend_std_get_property_ptr_ptr,          /* get_property_ptr_ptr */
    NULL,                                   /* get */
    NULL,                                   /* set */
    zend_std_has_property,                  /* has_property */
    zend_std_unset_property,                /* unset_property */
    zend_std_has_dimension,                 /* has_dimension */
    zend_std_unset_dimension,               /* unset_dimension */
    zend_std_get_properties,                /* get_properties */
    zend_std_get_method,                    /* get_method */
    NULL,                                   /* call_method */
    zend_std_get_constructor,               /* get_constructor */
    zend_std_object_get_class_name,         /* get_class_name */
    zend_std_compare_objects,               /* compare_objects */
    zend_std_cast_object_tostring,          /* cast_object */
    NULL,                                   /* count_elements */
    zend_std_get_debug_info,                /* get_debug_info */
    zend_std_get_closure,                   /* get_closure */
    zend_std_get_gc,                        /* get_gc */
    NULL,                                   /* do_operation */
    NULL,                                   /* compare */
}

动态属性的存储

  • properties: 普通成员属性哈希表,key为动态属性名,value为动态属性值。对象建立之初这个值为NULL,主要是在动态定义属性时会用到。
  • 那么什么是动态属性呢?就是以前在类定义阶段未定义的属性,在运行期间动态添加的属性,如:
class A{
    public $name = 'jby';
}
$a = new A();
$a->age = 18;
  • 这里的age就是动态属性,而name是普通属性。
  • 基于以前讲过的查找普通属性的流程,咱们由特殊到通常地得出查找全部类型的对象属性的方式:
  • 在查找一个对象的属性的时候,会首先按照咱们以前讲过的查找普通属性的方式,首先找到偏移量offset,即类结构的zend_class_entry下的properties_info字段中的offset,而后根据这个偏移量offset到对象结构zend_object下的properties_table柔性数组中找。
  • 若是按照查找普通属性的方式没有找到,那么咱们再去zend_object下的properties字段继续查找动态属性便可,整理以下:
- 经过指针ce找到当前对象对应的类结构zend_class_entry
 - 取出当前类结构中的Hashtable property_info字段,这个字段是一个哈希表,存有属性的信息。
 - 将要查找的属性名做为key,到哈希表中找到对应的value,即zend_property_info结构体,并取出结构体中的offset字段
 - 到当前对象zend_object结构体中,经过内存地址计算(柔性数组的起始地址+offset)就能够获得所要访问的当前对象的某个普通属性的值
 - 若是以上都没有找到,说明它是一个动态属性,那么就去zend_object下的properties哈希表中查找,属性名做为key,到这个哈希表中查找对应的value便可
相关文章
相关标签/搜索