### 3.4.2 對象
對象是類的實例,PHP中要創建一個類的實例,必須使用 new 關鍵字。類應在被實例化之前定義(某些情況下則必須這樣,比如3.4.1最后那幾個例子)。
#### 3.4.2.1 對象的數據結構
對象的數據結構非常簡單:
```c
typedef struct _zend_object 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]; //普通屬性值數組
};
```
幾個主要的成員:
__(1)handle:__ 一次request期間對象的編號,每個對象都有一個唯一的編號,與創建先后順序有關,主要在垃圾回收時用,下面會詳細說明。
__(2)ce:__ 所屬類的zend_class_entry。
__(3)handlers:__ 這個保存的對象相關操作的一些函數指針,比如成員屬性的讀寫、成員方法的獲取、對象的銷毀/克隆等等,這些操作接口都有默認的函數。
```c
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 */
}
```
> __Note:__ 這些handler用于操作對象(如:設置、讀取屬性),std_object_handlers是PHP定義的默認、標準的處理函數,在擴展中可以自定義handler,比如:重定義write_property,這樣設置一個對象的屬性時將調用擴展自己定義的處理函數,讓擴展擁有了更高的控制權限。
>
> 需要注意的是:const zend_object_handlers *handlers,這里的handlers指針加了const修飾符,const修飾的是handlers**指向的對象**,而不是handlers指針本身,所以擴展中可以將一個對象的handlers修改為另一個zend_object_handlers指針,但無法修改zend_object_handlers中的值,比如:`obj->handlers->write_property = xxx`將報錯,而:`obj->handlers = xxx`則是可以的。
__(4)properties:__ 普通成員屬性哈希表,對象創建之初這個值為NULL,主要是在動態定義屬性時會用到,與properties_table有一定關系,下一節我們將單獨說明,這里暫時忽略。
__(5)properties_table:__ 成員屬性數組,還記得我們在介紹類一節時提過非靜態屬性存儲在對象結構中嗎?就是這個properties_table!注意,它是一個數組,`zend_object`是個變長結構體,分配時會根據非靜態屬性的數量確定其大小。
#### 3.4.2.2 對象的創建
PHP中通過`new + 類名`創建一個類的實例,我們從一個例子分析下對象創建的過程中都有哪些操作。
```php
class my_class
{
const TYPE = 90;
public $name = "pangudashu";
public $ids = array();
}
$obj = new my_class();
```
類的定義就不用再說了,我們只看`$obj = new my_class();`這一句,這條語句包括兩部分:實例化類、賦值,下面看下實例化類的語法規則:
```c
new_expr:
T_NEW class_name_reference ctor_arguments
{ $$ = zend_ast_create(ZEND_AST_NEW, $2, $3); }
| T_NEW anonymous_class
{ $$ = $2; }
;
```
從語法規則可以很直觀的看出此語法的兩個主要部分:類名、參數列表,編譯器在解析到實例化類時就創建一個`ZEND_AST_NEW`類型的節點,后面編譯為opcodes的過程我們不再細究,這里直接看下最終生成的opcodes。

你會發現實例化類產生了兩條opcode(實際可能還會更多):ZEND_NEW、ZEND_DO_FCALL,除了創建對象的操作還有一條函數調用的,沒錯,那條就是調用`構造方法`的操作。
根據opcode、操作數類型可知`ZEND_NEW`對應的處理handler為`ZEND_NEW_SPEC_CONST_HANDLER()`:
```c
static int ZEND_NEW_SPEC_CONST_HANDLER(zend_execute_data *execute_data)
{
zval object_zval;
zend_function *constructor;
zend_class_entry *ce;
...
//第1步:根據類名查找zend_class_entry
ce = zend_fetch_class_by_name(Z_STR_P(EX_CONSTANT(opline->op1)), ...);
...
//第2步:創建&初始化一個這個類的對象
if (UNEXPECTED(object_init_ex(&object_zval, ce) != SUCCESS)) {
HANDLE_EXCEPTION();
}
//第3步:獲取構造方法
//獲取構造方法函數,實際就是直接取zend_class_entry.constructor
//get_constructor => zend_std_get_constructor()
constructor = Z_OBJ_HT(object_zval)->get_constructor(Z_OBJ(object_zval));
if (constructor == NULL) {
...
//此opcode之后還有傳參、調用構造方法的操作
//所以如果沒有定義構造方法則直接跳過這些操作
ZEND_VM_JMP(OP_JMP_ADDR(opline, opline->op2));
}else{
//定義了構造方法
//初始化調用構造函數的zend_execute_data
zend_execute_data *call = zend_vm_stack_push_call_frame(...);
call->prev_execute_data = EX(call);
EX(call) = call;
...
}
}
```
從上面的創建對象的過程看整個流程主要分為三步:首先是根據類名在EG(class_table)中查找對應zend_class_entry、然后是創建并初始化一個對象、最后是初始化調用構造函數的zend_execute_data。
我們再具體看下第2步創建、初始化對象的操作,`object_init_ex(&object_zval, ce)`最終調用的是`_object_and_properties_init()`。
```c
//zend_API.c
ZEND_API int _object_and_properties_init(zval *arg, zend_class_entry *class_type, ...)
{
//檢查類是否可以實例化
...
//用戶自定義的類create_object都是NULL
//只有PHP幾個內部的類有這個值,比如exception、error等
if (class_type->create_object == NULL) {
//分配一個對象
ZVAL_OBJ(arg, zend_objects_new(class_type));
...
//初始化成員屬性
object_properties_init(Z_OBJ_P(arg), class_type);
} else {
//調用自定義的創建object的鉤子函數
ZVAL_OBJ(arg, class_type->create_object(class_type));
}
return SUCCESS;
}
```
還記得上一節介紹zend_class_entry時有幾個自定義的鉤子函數嗎?如果定義了`create_object`這個地方就會調用自定義的函數來創建zend_object,這種情況通常發生在內核或擴展中定義的內部類(當然用戶自定義類也可以修改,但一般不會那樣做);用戶自定義類在這個地方又具體分了兩步:分配對象結構、初始化成員屬性,我們繼續看下這里面的處理。
__(1)分配對象結構:zend_object__
```c
//zend_objects.c
ZEND_API zend_object *zend_objects_new(zend_class_entry *ce)
{
//分配zend_object
zend_object *object = emalloc(sizeof(zend_object) + zend_object_properties_size(ce));
zend_object_std_init(object, ce);
//設置對象的操作handler為std_object_handlers
object->handlers = &std_object_handlers;
return object;
}
```
有個地方這里需要特別注意:分配對象結構的內存并不僅僅是zend_object的大小。我們在3.4.2.1介紹properties_table時說過這是一個變長數組,它用來存放非靜態屬性的值,所以分配zend_object時需要加上非靜態屬性所占用的內存大小:`zend_object_properties_size()`,根據普通非靜態屬性個數確定,如果沒有定義__get()、__set()等魔術方法則占用內存就是: __屬性數*sizeof(zval)__ ,如果定義了這些魔術方法那么會多分配一個zval的空間,這個多出來zval的用途下面介紹成員屬性的讀寫時再作說明。
另外這里還有一個關鍵操作:__將object編號并插入EG(objects_store).object_buckets數組__。zend_object有個成員:handle,這個值在一次request期間所有實例化對象的編號,每調用`zend_objects_new()`實例化一個對象就會將其插入到object_buckets數組中,其在數組中的下標就是handle。這個過程是在`zend_objects_store_put()`中完成的。
```c
//zend_objects_API.c
ZEND_API void zend_objects_store_put(zend_object *object)
{
int handle;
if (EG(objects_store).free_list_head != -1) {
//這種情況主要是gc中會將中間一些object銷毀,空出一些bucket位置
//然后free_list_head就指向了第一個可用的bucket位置
//后面可用的保存在第一個空閑bucket的handle中
handle = EG(objects_store).free_list_head;
EG(objects_store).free_list_head = GET_OBJ_BUCKET_NUMBER(EG(objects_store).object_buckets[handle]);
} else {
if (EG(objects_store).top == EG(objects_store).size) {
//擴容
}
//遞增加1
handle = EG(objects_store).top++;
}
object->handle = handle;
//存入object_buckets數組
EG(objects_store).object_buckets[handle] = object;
}
typedef struct _zend_objects_store {
zend_object **object_buckets; //對象數組
uint32_t top; //當前全部object數
uint32_t size; //object_buckets大小
int free_list_head; //第一個可用object_buckets位置
} zend_objects_store;
```
將所有的對象保存在`EG(objects_store).object_buckets`中的目的是用于垃圾回收(不確定是不是還有其它的作用),防止出現循環引用而導致內存泄漏的問題,這個機制后面章節會單獨介紹,這里只要記得有這么個東西就行了。
__(2)初始化成員屬性__
```c
ZEND_API void object_properties_init(zend_object *object, zend_class_entry *class_type)
{
if (class_type->default_properties_count) {
zval *src = class_type->default_properties_table;
zval *dst = object->properties_table;
zval *end = src + class_type->default_properties_count;
//將非靜態屬性值從:
//zend_class_entry.default_properties_table復制到zend_object.properties_table
do {
ZVAL_COPY(dst, src);
src++;
dst++;
} while (src != end);
object->properties = NULL;
}
}
```
這一步操作是將非靜態屬性的值從`zend_class_entry.default_properties_table -> zend_object.properties_table`,當然這里不是硬拷貝,而是淺復制(增加引用),兩者當前指向的value還是同一份,除非對象試圖改寫指向的屬性值,那時將觸發寫時復制機制重新拷貝一份。
上面那個例子,類有兩個普通屬性:$name、$ids,假如我們實例化了兩個對象,那么zend_class_entry與zend_object中普通屬性值的關系如下圖所示。

以上就是實例化一個對象的過程,總結一下具體的步驟:
* __step1:__ 首先根據類名去EG(class_table)中找到具體的類,即zend_class_entry
* __step2:__ 分配zend_object結構,一起分配的還有普通非靜態屬性值的內存
* __step3:__ 初始化對象的非靜態屬性,將屬性值從zend_class_entry淺復制到對象中
* __step4:__ 查找當前類是否定義了構造函數,如果沒有定義則跳過執行構造函數的opcode,否則為調用構造函數的執行進行一些準備工作(分配zend_execute_data)
* __step5:__ 實例化完成,返回新實例化的對象(如果返回的對象沒有變量使用則直接釋放掉了)
#### 3.4.2.3 成員屬性的讀寫
普通成員屬性的讀寫處理handler分別為`zend_object.handlers`中的:read_property、write_property,默認對應的函數為:zend_std_read_property()、zend_std_write_property(),訪問獲取修改一個普通成員屬性時就是由這兩個函數完成的。
__(1)讀取屬性:__
通過對象或方法內通過$this訪問屬性,比如:`echo $obj->name;`,具體的實現:
```c
zval *zend_std_read_property(zval *object, zval *member, int type, void **cache_slot, zval *rv)
{
zend_object *zobj;
uint32_t property_offset;
zobj = Z_OBJ_P(object);
//根據屬性名在zend_class.zend_property_info中查找zend_property_info,得到屬性值在zend_object中的存儲offset
//注意:zend_get_property_offset()會對屬性的可見性(public、private、protected)進行驗證
property_offset = zend_get_property_offset(zobj->ce, Z_STR_P(member), (type == BP_VAR_IS) || (zobj->ce->__get != NULL), cache_slot);
if (EXPECTED(property_offset != ZEND_WRONG_PROPERTY_OFFSET)) {
if (EXPECTED(property_offset != ZEND_DYNAMIC_PROPERTY_OFFSET)) {
//普通屬性,直接根據offset取到屬性值:((zval*)((char*)(zobj) + offset))
retval = OBJ_PROP(zobj, property_offset);
} else if (EXPECTED(zobj->properties != NULL)) {
//動態屬性的情況,沒有在類中顯式定義的屬性,后面一節會單獨介紹
....
}
} else if (UNEXPECTED(EG(exception))) {
...
}
//沒有找到屬性
//調用魔術方法:__isset()
if ((type == BP_VAR_IS) && zobj->ce->__isset) {
...
}
//調用魔術方法:__get()
if (zobj->ce->__get) {
zend_long *guard = zend_get_property_guard(zobj, Z_STR_P(member));
...
if(!((*guard) & IN_ISSET)){
*guard |= IN_ISSET;
zend_std_call_issetter(&tmp_object, member, &tmp_result);
*guard &= ~IN_ISSET;
...
}
}
...
}
```
普通成員屬性的查找比較容易理解,首先是從zend_class的屬性信息哈希表中找到zend_property_info,并判斷其可見性(public、private、protected),如果可以訪問則直接根據屬性的offset在zend_object.properties_table數組中取到屬性值,如果沒有在屬性哈希表中找到且定義了__get()魔術方法則會調用__get()方法處理。
> __Note:__ 如果類存在__get()方法,則在實例化對象分配屬性內存(即:properties_table)時會多分配一個zval,類型為HashTable,每次調用__get($var)時會把輸入的$var名稱存入這個哈希表,這樣做的目的是防止循環調用,舉個例子:
>
> ***public function __get($var) { return $this->$var; }***
>
> 這種情況是調用__get()時又訪問了一個不存在的屬性,也就是會在__get()方法中遞歸調用,如果不對請求的$var作判斷則將一直遞歸下去,所以在調用__get()前首先會判斷當前$var是不是已經在__get()中了,如果是則不會再調用__get(),否則會把$var作為key插入那個HashTable,然后將哈希值設置為:*guard |= IN_ISSET,調用完__get()再把哈希值設置為:*guard &= ~IN_ISSET。
>
> 這個HashTable不僅僅是給__get()用的,其它魔術方法也會用到,所以其哈希值類型是zend_long,不同的魔術方法占不同的bit位;其次,并不是所有的對象都會額外分配這個HashTable,在對象創建時會根據 ***zend_class_entry.ce_flags*** 是否包含 ***ZEND_ACC_USE_GUARDS*** 確定是否分配,在類編譯時如果發現定義了__get()、__set()、__unset()、__isset()方法則會將ce_flags打上這個掩碼。
__(2)設置屬性:__
與讀取屬性不同,設置屬性是對屬性的修改操作,比如:`$obj->name = "pangudashu";`,看下具體的實現過程:
```c
ZEND_API void zend_std_write_property(zval *object, zval *member, zval *value, void **cache_slot)
{
zend_object *zobj;
uint32_t property_offset;
zobj = Z_OBJ_P(object);
//與讀取屬性相同
property_offset = zend_get_property_offset(zobj->ce, Z_STR_P(member), (zobj->ce->__set != NULL), cache_slot);
if (EXPECTED(property_offset != ZEND_WRONG_PROPERTY_OFFSET)) {
if (EXPECTED(property_offset != ZEND_DYNAMIC_PROPERTY_OFFSET)) {
//普通屬性
variable_ptr = OBJ_PROP(zobj, property_offset);
if (Z_TYPE_P(variable_ptr) != IS_UNDEF) {
goto found;
}
} else if (EXPECTED(zobj->properties != NULL)) {
//動態屬性哈希表已經初始化,直接插入zobj->properties哈希表,后面單獨介紹
...
if ((variable_ptr = zend_hash_find(zobj->properties, Z_STR_P(member))) != NULL) {
found:
//賦值操作,與普通變量的操作相同
zend_assign_to_variable(variable_ptr, value, IS_CV);
goto exit;
}
}
} else if (UNEXPECTED(EG(exception))) {
...
}
//沒有找到屬性
//如果定義了__set()則調用
if (zobj->ce->__set) {
//與__get()相同,也會判斷set的變量名是否已經在__set()中
...
ZVAL_COPY(&tmp_object, object);
(*guard) |= IN_SET; //防止循環__set()
if (zend_std_call_setter(&tmp_object, member, value) != SUCCESS) {
}
(*guard) &= ~IN_SET;
}else if (EXPECTED(property_offset != ZEND_WRONG_PROPERTY_OFFSET)) {
...
}
}
```
首先與讀取屬性的操作相同:先找到zend_property_info,判斷其可見性,然后根據offset取到具體的屬性值,最后對其進行賦值修改。
> __Note:__ 屬性讀寫操作的函數中有一個cache_slot的參數,它的作用涉及PHP的一個緩存機制:運行時緩存,后面會單獨介紹。
#### 3.4.2.4 對象的復制
PHP中普通變量的復制可以通過直接賦值完成,比如:
```php
$a = array();
$b = $a;
```
但是對象無法這么進行復制,僅僅通過賦值傳遞對象,它們指向的都是同一個對象,修改時也不會發生硬拷貝。比如上面這個例子,我們把`$a`賦值給`$b`,然后如果我們修改`$b`的內容,那么這時候會進行value分離,`$a`的內容是不變的,但是如果是把一個對象賦值給了另一個變量,這倆對象不管哪一個修改另外一個都隨之改變。
```php
class my_class
{
public $arr = array();
}
$a = new my_class;
$b = $a;
$b->arr[] = 1;
var_dump($a === $b);
====================
輸出:bool(true)
```
還記得我們在《2.1.3.2 寫時復制》一節講過zval有個類型掩碼: __type_flag__ 嗎?其中有個是否可復制的標識:__IS_TYPE_COPYABLE__ ,copyable的意思是當value發生duplication時是否需要或能夠copy,而object的類型是不能復制(不清楚的可以翻下前面的章節),所以我們不能簡單的通過賦值語句進行對象的復制。
PHP提供了另外一個關鍵詞來實現對象的復制:__clone__。
```php
$copy_of_object = clone $object;
```
`clone`出的對象就與原來的對象完全隔離了,各自修改都不會相互影響,另外如果類中定義了`__clone()`魔術方法,那么在`clone`時將調用此函數。
`clone`的實現比較簡單,通過`zend_object.clone_obj`(即:`zend_objects_clone_obj()`)完成。
```c
//zend_objects.c
ZEND_API zend_object *zend_objects_clone_obj(zval *zobject)
{
zend_object *old_object;
zend_object *new_object;
old_object = Z_OBJ_P(zobject);
//重新分配一個zend_object
new_object = zend_objects_new(old_object->ce);
//淺復制properties_table、properties
//如果定義了__clone()則調用此方法
zend_objects_clone_members(new_object, old_object);
return new_object;
}
```
#### 3.4.2.5 對象比較
當使用比較運算符(==)比較兩個對象變量時,比較的原則是:如果兩個對象的屬性和屬性值 都相等,而且兩個對象是同一個類的實例,那么這兩個對象變量相等;而如果使用全等運算符(===),這兩個對象變量一定要指向某個類的同一個實例(即同一個對象)。
PHP中對象間的"=="比較通過函數`zend_std_compare_objects()`處理。
```c
static int zend_std_compare_objects(zval *o1, zval *o2)
{
...
if (zobj1->ce != zobj2->ce) {
return 1; /* different classes */
}
if (!zobj1->properties && !zobj2->properties) {
//逐個比較properties_table
...
}else{
//比較properties
return zend_compare_symbol_tables(zobj1->properties, zobj2->properties);
}
}
```
"==="的比較通過函數`zend_is_identical()`處理,比較簡單,這里不再展開。
#### 3.4.2.6 對象的銷毀
object與string、array等類型不同,它是個復合類型,所以它的銷毀過程更加復雜,賦值、函數調用結束或主動unset等操作中如果發現object引用計數為0則將觸發銷毀動作。
```php
//情況1
$obj1 = new my_function();
$obj1 = 123; //此時將斷開對zend_object的引用,如果refcount=0則銷毀
//情況2
function xxxx(){
$obj1 = new my_function();
...
return null; //清理局部變量時如果發現$obj1引用為0則銷毀
}
//情況3
$obj1 = new my_function();
//整個腳本結束,清理全局變量時
//情況4
$obj1 = new my_function();
unset($obj1);
```
上面這幾個都是比較常見的會進行變量銷毀的情況,銷毀一個對象由`zend_objects_store_del()`完成,銷毀的過程主要是清理成員屬性、從EG(objects_store).object_buckets中刪除、釋放zend_object內存等等。
```c
//zend_objects_API.c
ZEND_API void zend_objects_store_del(zend_object *object)
{
//這個函數if嵌套寫的很挫...
...
if (GC_REFCOUNT(object) > 0) {
GC_REFCOUNT(object)--;
return;
}
...
//調用dtor_obj,默認zend_objects_destroy_object()
//接著調用free_obj,默認zend_object_std_dtor()
object->handlers->dtor_obj(object);
object->handlers->free_obj(object);
...
ptr = ((char*)object) - object->handlers->offset;
efree(ptr);
}
```
另外,在減少refcount時如果發現object的引用計數大于0那么并不是什么都不做了,還記得2.1.3.4介紹的垃圾回收嗎?PHP變量類型有的會因為循環引用導致正常的gc無法生效,這種類型的變量就有可能成為垃圾,所以會對這些類型的`zval.u1.type_flag`打上`IS_TYPE_COLLECTABLE`標簽,然后在減少引用時即使refcount大于0也會啟動垃圾檢查,目前只有object、array兩種類型會使用這種機制。
- 前言
- 第1章 PHP基本架構
- 1.1 PHP簡介
- 1.2 PHP7的改進
- 1.3 FPM
- 1.3.1 概述
- 1.3.2 基本實現
- 1.3.3 FPM的初始化
- 1.3.4 請求處理
- 1.3.5 進程管理
- 1.4 PHP執行的幾個階段
- 第2章 變量
- 2.1 變量的內部實現
- 2.2 數組
- 2.3 靜態變量
- 2.4 全局變量
- 2.5 常量
- 第3章 Zend虛擬機
- 3.1 PHP代碼的編譯
- 3.1.1 詞法解析、語法解析
- 3.1.2 抽象語法樹編譯流程
- 3.2 函數實現
- 3.2.1 內部函數
- 3.2.2 用戶函數的實現
- 3.3 Zend引擎執行流程
- 3.3.1 基本結構
- 3.3.2 執行流程
- 3.3.3 函數的執行流程
- 3.3.4 全局execute_data和opline
- 3.4 面向對象實現
- 3.4.1 類
- 3.4.2 對象
- 3.4.3 繼承
- 3.4.4 動態屬性
- 3.4.5 魔術方法
- 3.4.6 類的自動加載
- 3.5 運行時緩存
- 3.6 Opcache
- 3.6.1 opcode緩存
- 3.6.2 opcode優化
- 3.6.3 JIT
- 第4章 PHP基礎語法實現
- 4.1 類型轉換
- 4.2 選擇結構
- 4.3 循環結構
- 4.4 中斷及跳轉
- 4.5 include/require
- 4.6 異常處理
- 第5章 內存管理
- 5.1 Zend內存池
- 5.2 垃圾回收
- 第6章 線程安全
- 6.1 什么是線程安全
- 6.2 線程安全資源管理器
- 第7章 擴展開發
- 7.1 概述
- 7.2 擴展的實現原理
- 7.3 擴展的構成及編譯
- 7.3.1 擴展的構成
- 7.3.2 編譯工具
- 7.3.3 編寫擴展的基本步驟
- 7.3.4 config.m4
- 7.4 鉤子函數
- 7.5 運行時配置
- 7.5.1 全局變量
- 7.5.2 ini配置
- 7.6 函數
- 7.6.1 內部函數注冊
- 7.6.2 函數參數解析
- 7.6.3 引用傳參
- 7.6.4 函數返回值
- 7.6.5 函數調用
- 7.7 zval的操作
- 7.7.1 新生成各類型zval
- 7.7.2 獲取zval的值及類型
- 7.7.3 類型轉換
- 7.7.4 引用計數
- 7.7.5 字符串操作
- 7.7.6 數組操作
- 7.8 常量
- 7.9 面向對象
- 7.9.1 內部類注冊
- 7.9.2 定義成員屬性
- 7.9.3 定義成員方法
- 7.9.4 定義常量
- 7.9.5 類的實例化
- 7.10 資源類型
- 7.11 經典擴展解析
- 7.8.1 Yaf
- 7.8.2 Redis
- 第8章 命名空間
- 8.1 概述
- 8.2 命名空間的定義
- 8.2.1 定義語法
- 8.2.2 內部實現
- 8.3 命名空間的使用
- 8.3.1 基本用法
- 8.3.2 use導入
- 8.3.3 動態用法
- 附錄
- break/continue按標簽中斷語法實現
- defer推遲函數調用語法的實現
- 一起線上事故引發的對PHP超時控制的思考