【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便可
相關文章
相關標籤/搜索