PHP7 核心 Object 深入理解
PHP5
#依照慣例,我先帶大家回顧PHP5時的zend_object(此部分內容之前的文章中也有涉及,如果熟悉可以跳過), 之前如果有興趣也可以看看我10年前寫的深入理解PHP原理之對象.
PHP5中,對象的定義如下:
typedef struct _zend_object { zend_class_entry *ce; HashTable *properties; zval **properties_table; HashTable *guards; } zend_object;
其中ce儲存了這個物件所屬的類, 關於properties_table和properties, properties_table是申明的屬性,properties是動態屬性,也就是例如:
<?php class Foo { public $a = 'defaul property'; } $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 { long lval; double dval; struct { char *val; int len; } str; HashTable *ht; zend_object_value obj; } zvalue_value;
對於字串和數組,zval中都直接保存它們的指針,而對於object卻是zend_object_value的結構體:
typedef unsigned int zend_object_handle;
typedef struct _zend_object_value { zend_object_handle handle; const zend_object_handlers *handlers; } zend_object_value;
真正取得物件是需要透過這個zend_object_handle,也就是一個int的索引去全域的object buckets中尋找
ZEND_API void *zend_object_store_get_object_by_handle(zend_object_handle handle TSRMLS_DC) { return EG(objects_store).object_buckets[handle].bucket.obj.object; }
而EG(objects_store).object_buckets則是一個數組,保存著:
typedef struct _zend_object_store_bucket { zend_bool destructor_called; zend_bool valid; zend_uchar apply_count; union _store_bucket { struct _store_object { void *object; zend_objects_store_dtor_t dtor; zend_objects_free_object_storage_t free_storage; zend_objects_store_clone_t clone; const zend_object_handlers *handlers; zend_uint refcount; gc_root_buffer *buffered; } obj; struct { int next; } 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(); //o1.refcount == 1, object.refcount == 1 $o2 = $o1; //o1.refcount == o2.refcoun == 2; object.refcount = 1; $o3 = &$o2; //o3.isref == o2.isref==1 //o3.refcount == o2.refcount == 2 //o1.isref == 0; o1.refcount == 1 //object.refcount == 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,如我前面的文章深入理解PHP7核心之ZVAL所說, zval中直接保存了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]; };
而EG(objects_store)也只是簡單的保存了一個zend_object**等指標:
typedef struct _zend_objects_store { zend_object **object_buckets; uint32_t top; uint32_t size; int free_list_head; } 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_Now.
##define SEPARATE_ZVAL(zv) do { \ zval *_zv = (zv); \ if (Z_REFCOUNTED_P(_zv) || \ Z_IMMUTABLE_P(_zv)) { \ if (Z_REFCOUNT_P(_zv) > 1) { \ if (Z_COPYABLE_P(_zv) || \ Z_IMMUTABLE_P(_zv)) { \ if (!Z_IMMUTABLE_P(_zv)) { \ Z_DELREF_P(_zv); \ } \ zval_copy_ctor_func(_zv); \ } else if (Z_ISREF_P(_zv)) { \ Z_DELREF_P(_zv); \ ZVAL_DUP(_zv, Z_REFVAL_P(_zv)); \ } \ } \ } \ } while (0)
如果不是Z_COPYABLE_P, 那麼就不會發生寫入時分離。
這裡有的同學會問,那既然已經在zval中直接保存了zend_object*了,那為啥還需要EG(objects_store)呢?
這裡有2個主要原因:
1. 我們需要在PHP請求結束的時候保證所有的物件的析構函數都被調用,因為object存在循環引用的情況,那如何快速的遍歷所有存活的物件呢? EG(objects_store)是個很不錯的選擇。
2. 在PHPNG開發的時候,為了保證最大向後相容,我們還是需要保證取得一個物件的handle的介面, 並且這個handle還是要保證原有的語意。
但實際上呢, 其實EG(objects_store)已經沒啥太大的用處了, 我們是可以在將來去掉它的。
好,接下來出現了另一個問題,我們再看看zend_object的定義, 注意到末尾的properties_table[1], 也就是說,我們現在會把object的屬性跟物件一起分配記憶體。這樣做對緩存友好。但帶來一個改變就是, zend_object這個結構體現在是可能變長的。
那在當時寫PHPNG的時候就給我帶來了一個問題, 在PHP5時代,很多的自訂物件是這麼定義的(mysqli為例):
typedef struct _mysqli_object { zend_object zo; void *ptr; HashTable *prop_handler; } mysqli_object; /* extends zend_object */
也就是说zend_object都在自定义的内部类的头部,这样当然有一个好处是可以很方便的做cast, 但是因为目前zend_object变成变长了,并且更严重的是你并不知道用户在PHP继承了你这个类以后,他新增了多少属性的定义。
于是没有办法,在写PHPNG的时候,我做了大量的调整如下(体力活):
typedef struct _mysqli_object { void *ptr; HashTable *prop_handler; zend_object zo; } mysqli_object; /* extends zend_object */
也就是把zend_object从头部,挪到了尾部,那为了可以从zend_object取得自定义对象,我们需要新增定义:
static inline mysqli_object *php_mysqli_fetch_object(zend_object *obj) { return (mysqli_object *)((char*)(obj) - XtOffsetOf(mysqli_object, zo)); }
这样类似的代码大家应该可以在很多使用了自定义对象的扩展中看到。
这样一来就规避了这个问题, 而在实际的分配自定义对象的时候,我们也需要采用如下的方法:
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) { guards = Z_PTR(zobj->properties_table[zobj->ce->default_properties_count]); .... }
从而可以在大部分情况下,节省一个指针的内存分配。
最后就是, PHP7中在取一个对象的类的时候,就会非常方便了, 直接zvalu.value.obj->ce即可,一些类所自定的handler也就可以很便捷的访问到了, 性能提升明显。
以上是PHP7 核心 Object 深入理解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

JWT是一種基於JSON的開放標準,用於在各方之間安全地傳輸信息,主要用於身份驗證和信息交換。 1.JWT由Header、Payload和Signature三部分組成。 2.JWT的工作原理包括生成JWT、驗證JWT和解析Payload三個步驟。 3.在PHP中使用JWT進行身份驗證時,可以生成和驗證JWT,並在高級用法中包含用戶角色和權限信息。 4.常見錯誤包括簽名驗證失敗、令牌過期和Payload過大,調試技巧包括使用調試工具和日誌記錄。 5.性能優化和最佳實踐包括使用合適的簽名算法、合理設置有效期、

如何在系統重啟後自動設置unixsocket的權限每次系統重啟後,我們都需要執行以下命令來修改unixsocket的權限:sudo...

靜態綁定(static::)在PHP中實現晚期靜態綁定(LSB),允許在靜態上下文中引用調用類而非定義類。 1)解析過程在運行時進行,2)在繼承關係中向上查找調用類,3)可能帶來性能開銷。

在Docker環境中使用PECL安裝擴展時報錯的原因及解決方法在使用Docker環境時,我們常常會遇到一些令人頭疼的問�...

PHP的魔法方法有哪些? PHP的魔法方法包括:1.\_\_construct,用於初始化對象;2.\_\_destruct,用於清理資源;3.\_\_call,處理不存在的方法調用;4.\_\_get,實現動態屬性訪問;5.\_\_set,實現動態屬性設置。這些方法在特定情況下自動調用,提升代碼的靈活性和效率。

PHP和Python各有優勢,選擇依據項目需求。 1.PHP適合web開發,尤其快速開發和維護網站。 2.Python適用於數據科學、機器學習和人工智能,語法簡潔,適合初學者。

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

在同一系統中同時運行多個PHP版本是一個常見的需求,特別是當不同項目依賴於不同版本的PHP時。如何在同一台...
