深入理解PHP7核心之OBJECT

2022-07-08 11:33:12 字數 4451 閱讀 7287

今天我來講講object(物件)的一些變化。

php5中,物件的定義如下:

typedef struct_zend_object zend_object;

其中ce儲存了這個物件所屬的類, 關於properties_table和properties, properties_table是申明的屬性,properties是動態屬性,也就是比如:

<?php

class foo

$a = new foo();

$a->b = 'dynamic property';

因為在foo的定義中,我們申明了public $a, 那麼$a就是已知的申明屬性,它的可見性,包括在properties_table中儲存的位置都是在申明後就確定的。

而$a->b, 是我們動態給新增的屬性,它不屬於已經申明的屬性,這個會儲存在properties中。

其實從型別上也能看出來, properties_table是zval*的陣列,而properties是hashtable。

guards主要用在魔術方法呼叫的時候巢狀保護, 比如__isset/__get/__set。

總體來說, zend_object(以下簡稱object)在php5中其實是一種相對特殊的存在, 在php5中,只有resource和object是引用傳遞,也就是說在賦值,傳遞的時候都是傳遞的本身,也正因為如此,object和resource除了使用了zval的引用計數以外,還採用了一套獨立自身的計數系統。

這個我們從zval中也能看出object和其他的類似字串的的不同:

typedef union _zvalue_value str;

hashtable*ht;

zend_object_valueobj;

} zvalue_value;

對於字串和陣列,zval中都直接儲存它們的指標,而對於object卻是乙個zend_object_value的結構體:

typedef unsigned int zend_object_handle;

typedef struct_zend_object_value zend_object_value;

真正獲取物件是需要通過這個zend_object_handle,也就是乙個int的索引去全域性的object buckets中查詢:

zend_apivoid *zend_object_store_get_object_by_handle(zend_object_handle handletsrmls_dc)

而eg(objects_store).object_buckets則是乙個陣列,儲存著:

typedef struct_zend_object_store_bucket obj;

struct free_list;

} bucket;

} zend_object_store_bucket;

其中,zend_object_store_bucket.bucket.obj.object才儲存著真正的zend_object的指標,注意到此處是void *, 這是因為我們很多擴充套件的自定義物件,也是可以儲存在這裡的。

另外我們也注意到zend_object_store_bueckt.bucket.obj.refcount, 這個既是我剛剛講的object自身的引用計數,也就是zval有一套自己的引用計數,object也有一套引用計數。

<?php

$o1 = new stdclass();

== 1, object.refcount == 1

$o2 = $o1;

== o2.refcoun == 2; object.refcount = 1;

$o3 = &$o2;

== o2.isref==1

== o2.refcount == 2

== 0; o1.refcount == 1

== 2

這樣,可以讓object可以保證不同於普通的zval的cow機制,可以保證object可以全域性傳引用。

可見,從乙個zval到取到實際的object,我們需要首先獲取zval.value.obj.handle, 然後拿著這個索引再去eg(objects_store)查詢,效率比較低下。

對於另外乙個常見的操作,就是獲取乙個zval物件的類的時候,我們也需要需要呼叫乙個函式:

#define z_objce(zval) zend_get_class_entry(&(zval) tsrmls_cc)

到了php7,如我前面的文章深入理解php7核心之zval所說, zval中直接儲存了zend_object物件的指標:

struct_zend_object ;

而eg(objects_store)也只是簡單的儲存了乙個zend_object**等指標:

typedef struct_zend_objects_store zend_objects_store;

而對於前面的cow的例子,對於is_object來說, 用is_type_copyable來區分,也就是,當發生cow的時候,如果這個型別沒有設定 is_type_copyable,那麼就不會發生"複製".

#define is_array_ex (is_array | ((is_type_refcounted | is_type_collectable | is_type_copyable) << z_type_flags_shift))

#define is_object_ex (is_object | ((is_type_refcounted | is_type_collectable) << z_type_flags_shift))

如上,大家可以看到對於array來說定義了is_type_refcounted, is_type_collectable和is_type_copyable, 但是對於object, 則缺少了is_type_copyable.

在separate_zval中:

#define separate_zval(zv) do \

zval_copy_ctor_func(_zv); \

} else if (z_isref_p(_zv)) \

} \} \

} while (0)

如果不是z_copyable_p, 那麼就不發生寫時分離。

這裡有的同學會問,那既然已經在zval中直接儲存了zend_object*了,那為啥還需要eg(objects_store)呢?

這裡有2個主要原因:

但實際上來說呢, 其實eg(objects_store)已經沒啥太大的用處了, 我們是可以在將來去掉它的。

好,接下來出現了另外乙個問題,我們再看看zend_object的定義, 注意到末尾的properties_table[1], 也就是說,我們現在會把object的屬性跟物件一起分配記憶體。這樣做對快取友好。 但帶來乙個變化就是, zend_object這個結構體現在是可能變長的。

那在當時寫phpng的時候就給我帶來了乙個問題, 在php5時代,很多的自定義物件是這麼定義的(mysqli為例):

typedef struct_mysqli_object mysqli_object; /* extends zend_object */

也就是說zend_object都在自定義的內部類的頭部,這樣當然有乙個好處是可以很方便的做cast, 但是因為目前zend_object變成變長了,並且更嚴重的是你並不知道使用者在php繼承了你這個類以後,他新增了多少屬性的定義。

於是沒有辦法,在寫phpng的時候,我做了大量的調整如下(體力活):

typedef struct_mysqli_object mysqli_object; /* extends zend_object */

也就是把zend_object從頭部,挪到了尾部,那為了可以從zend_object取得自定義物件,我們需要新增定義:

static inlinemysqli_object*php_mysqli_fetch_object(zend_object*obj)

這樣類似的**大家應該可以在很多使用了自定義物件的擴充套件中看到。

這樣一來就規避了這個問題, 而在實際的分配自定義物件的時候,我們也需要採用如下的方法:

obj = ecalloc(1, sizeof(mysqli_object) + zend_object_properties_size(class_type));

這塊,大家在寫擴充套件的時候,如果用到自定義的類,一定要注意。

而之前在php5中的guard, 我們也知道並不是所有的類都會申明魔術方法,在php5中把guard放在object中會在大部分情況下都是浪費記憶體, 所以在php7中會,我們會根據乙個類是否申明了魔術方法(is_obj_has_guards)來決定要不要分配,而具體的分配地方也放在了properties_table的末尾:

if (gc_flags(zobj) & is_obj_has_guards)

從而可以在大部分情況下,節省乙個指標的記憶體分配。

最後就是, php7中在取乙個物件的類的時候,就會非常方便了, 直接zvalu.value.obj->ce即可,一些類所自定的handler也就可以很便捷的訪問到了, 效能提公升明顯。

深入理解PHP7核心之Reference

上一章說過引用 reference 在php5的時候是乙個標誌位,而在php7以後我們把它變成了一種新的型別 is refernce.然而引用是一種很常見的應用,所以這個變化帶來了很多的變化,也給我們在做php7開發的時候,因為有的時候疏忽忘了處理這個型別,而帶來不少的bug.最簡單的情況,就是在處...

深入理解php核心

第二章 使用者 的執行 第三節 zend引擎與指令碼執行 第四節 小結 第三章 變數及資料型別 第二節 常量 第三節 預定義變數 第四節 靜態變數 第五節 型別提示的實現 第六節 變數的生命週期 第七節 資料型別轉換 第八節 小結 第四章 函式的實現 第二節 函式的定義,引數及返回值 第三節 函式的...

深入理解php核心

第二章 使用者 的執行 第三節 zend引擎與指令碼執行 第四節 小結 第三章 變數及資料型別 第二節 常量 第三節 預定義變數 第四節 靜態變數 第五節 型別提示的實現 第六節 變數的生命週期 第七節 資料型別轉換 第八節 小結 第四章 函式的實現 第二節 函式的定義,引數及返回值 第三節 函式的...