在PHP中,我們經常使用到資源類型變數。例如:mysql連線、檔案句柄等。這些變數無法使用標量來表示,那麼在Zend核心中是如何將PHP中的資源變數與C語言中的資源銜接的呢?
一、資源變數在PHP中的使用
$fp = fopen("test.txt", "rw"); var_dump($fp); fclose($fp);
列印結果:resource(5) of type (stream)
數字5:表示資源ID為5,具體意義後面介紹。
stream:資源型別名稱。
二、資源ID
核心中將註冊的資源變數儲存在一個HashTable中,並把資源所在HashTable中的key當作資源ID。
所以,實際上PHP中的資源變數實際儲存的是一個整數,透過這個ID找到HashTable中對應的資源。
#define Z_RESVAL(zval) (zval).value.lval #define Z_RESVAL_P(zval) Z_RESVAL(*zval) #define Z_RESVAL_PP(zval) Z_RESVAL(**zval)
上面的宏,是核心中ZE為資源變數賦值的API,看出確實是對整數變數的賦值。
三、資源類型名稱
為了區分資源類型,需要為我們定義的資源定義類型名稱。
#define MY_RES_NAME "my_resource" //资源类型名称,PHP通过var_dump打印资源变量时会看到这个名称 static int my_resource_descriptor; ZEND_MINIT_FUNCTION(jinyong) { my_resource_descriptor = zend_register_list_destructors_ex(NULL, NULL, MY_RES_NAME, module_number);//向内核中注册新的资源类型 }
ZEND_MINIT_FUNCTION(jinyong)會在PHP作為SAPI(例如,Apache的mod_php5擴充)被載入到記憶體時,會執行所有擴充的ZEND_MINIT_FUNCTION。
其中jinyong,是目前擴充的名字。例如此時擴充的名字就是jinyong
這裡為了方便理解,我們就把它認為是擴充在初始化時,會向核心註冊新的資源型別。
四、建立資源變數
資源型別已經註冊成功,也為資源定義了區分的型別名稱。現在可以使用這種資源的變數了。
實作PHP中的fopen函數:
PHP_FUNCTION(my_fopen) { zval *res; char *filename, *mode; int filename_strlen, mode_strlen; FILE *fp; if(zend_parse_parameters(ZEND_NUM_ARGS TSRMLS_CC, "s|s", &filename, &filename_strlen, &mode, &mode_strlen) == FAILURE){ RETURN_FALSE; } //此处省略了对参数的有效性验证 fp = fopen(filename, mode); ZEND_REGISTER_RESOURCE(res, fp, my_resource_descriptor);//向全局变量&EG(regular_list)中注册资源变量,并将对应HashTable的ID赋值给res RETURN_RESOURCE(res);//向PHP返回资源变量 }
#這裡,定義了PHP中名稱為my_fopen的函數。 my_fopen(string $file_name, string $mode)
實作PHP中的fclose函數:
PHP_FUNCTION(my_fclose) { zval *res; FILE *fp; if(zend_parse_parameters(ZEND_NUM_ARGS TSRMS_CC, "r", &res) == FAILURE){ RETURN_FALSE; } if(Z_TYPE_P(res) == IS_RESOURCE){//判断变量类型是否是资源类型 zend_hash_index_del(&EG(regular_list), Z_RESVAL_P(res));//EG就类似于PHP中的$_GLOBALS。在全局资源变量regular_list中删除对应ID的资源 }else{ php_error_docref(NULL TSRMLS_CC, E_WARNING, "参数必须是资源类型变量"); RETURN_FALSE; } RETURN_TRUE; }
定義了PHP中名稱為my_fclose的函數。 my_fclose($resource)
五、編譯、安裝擴展,重啟php-fpm或mod_php5等
六、PHP中使用自訂擴充功能中的方法
my_fwrite($fp, "aaTest"); var_dump($fp); my_fclose($fp); var_dump($fp);
可以正常,開啟和關閉資源。
七、我們在PHP中經常使用資料庫連接資源、檔案句柄資源,但他們通常無需我們手動釋放,也不會出現記憶體洩漏問題,這是如何實現的呢?
my_resource_descriptor = zend_register_list_destructors_ex(NULL, NULL, MY_RES_NAME, module_number);//向内核中注册新的资源类型
回到最開始的註冊資源類型,看到zend_register_list_destructors_ex的第一個參數,這個參數就是析構函式的指標。
那麼,如果需要實現自動釋放功能,只需要定義析構函數並傳遞函數指標即可。
再看一個問題:
$fp = fopen("test.txt", "rw"); var_dump($fp); //fclose($fp); 此处不使用fclose释放资源 unset($fp); //而是使用unset释放 //unset没有问题,会正常释放$fp变量。但$fp对应真正的打开文件资源句柄资源将永远释放不了,直至mod_php5或php-fpm重启 //可以看出,在注册资源类型时定义析构函数的必要性了
#定義析構函數:
static void php_myres_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC){//析构函数被调用时,会接受一个当前资源变量的参数 FILE *fp = (FILE*)rsrc->ptr; fclose(fp); } ZEND_MINIT_FUNCTION(jinyong) { my_resource_descriptor = zend_register_list_destructors_ex(php_myres_dtor, NULL, MY_RES_NAME, module_number); }
在PHP中,所謂資源變量,實際上都是透過儲存整數值,在到核心全域資源變數列表EG(regular_list)中找到對應的指針,並進行對應運算。
資源型別是一種特殊的變量,保存了外部資源的一個參考。資源是透過專門的函數來建立和使用的。
例如資料庫連接,打開文件,圖形畫布區域等。
資源類型其實只是一個整數,而核心可以根據這個整數值去一個類似資源池的地方尋找最終需要的資料。
範例1,檔案操作的範例:
程式碼範例:
<?php $file=fopen('a.txt','r');//使用fopen函数打开一个文件获取句柄。 fread($file,1024);//之后把该句柄传递给fread函数,即可对此文件进行后续操作。
範例2,資料庫操作的範例:
#程式碼範例:
<?php $result=mysql_query('select * from tbale');//mysql_query函数执行一条sql,若失败,返回false;成功,查询结果被缓存,并返回资源标识(类似:Resource id#42)即指向该资源的句柄。 mysql_num_row($result);//使用该句柄可以操作缓存中的资源,从而返回查询出来的条数 mysql_fetch_row($result);//使用该句柄可以操作缓存中的资源,从而返回查询结构
說明:
使用和銷毀資源的函數清單。
可以用is_resource()函數來測定變數是否為資源,而函數get_resource_type()則傳回該資源的類型。
而PHP資源變量,之所以不用擔心類似MYSQL連線未釋放問題,也是因為擴充中定義了析構方法,幫助自動釋放。
相關建議:
#以上是PHP資源類型實例分享的詳細內容。更多資訊請關注PHP中文網其他相關文章!