天天看點

PHP資源類型

在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)中找到對應的指針,并進行相應操作。
	而PHP資源變量,之是以不用擔心類似MYSQL連接配接未釋放問題,也是因為擴充中定義了析構方法,幫助自動釋放。