PHP中的资源类型

在PHP中,我们经常使用到资源类型变量。例如:mysql连接、文件句柄等。

这些变量无法使用标量来表示,那么在Zend内核中是如何将PHP中的资源变量与C语言中的资源衔接的呢?


一、资源变量在PHP中的使用

[php]  view plain copy
  1. $fp = fopen("test.txt""rw");  
  2.   
  3. var_dump($fp);  
  4.   
  5. fclose($fp);  

打印结果:resource(5) of type (stream)

数字5:表示资源ID为5,具体含义后面介绍。

stream:资源类型名称。


二、资源ID

内核中将注册的资源变量存储在一个HashTable中,并把资源所在HashTable中的key作为资源ID。

所以,实际上PHP中的资源变量实际存储的是一个整型,通过这个ID找到HashTable中对应的资源。

[cpp]  view plain copy
  1. #define Z_RESVAL(zval)          (zval).value.lval  
  2. #define Z_RESVAL_P(zval)        Z_RESVAL(*zval)  
  3. #define Z_RESVAL_PP(zval)       Z_RESVAL(**zval)  
上面的宏,是内核中ZE为资源变量赋值的API,看出确实是对整型变量的赋值。


三、资源类型名称

为了区分资源类型,需要为我们定义的资源定义类型名称。

[cpp]  view plain copy
  1. #define MY_RES_NAME "my_resource" //资源类型名称,PHP通过var_dump打印资源变量时会看到这个名称  
  2. static int my_resource_descriptor;  
  3.   
  4. ZEND_MINIT_FUNCTION(jinyong)  
  5. {  
  6.     my_resource_descriptor = zend_register_list_destructors_ex(NULL, NULL, MY_RES_NAME, module_number);//向内核中注册新的资源类型  
  7. }  

ZEND_MINIT_FUNCTION(jinyong)会在PHP作为SAPI(例如,Apache的mod_php5扩展)被加载到内存时,会执行所有扩展的ZEND_MINIT_FUNCTION。

其中jinyong,是当前扩展的名字。例如此时扩展的名字就是jinyong

这里为了方便理解,我们就把它认为是扩展在初始化时,会向内核中注册新的资源类型。


四、创建资源变量

资源类型已经注册成功,也为资源定义了区分的类型名称。现在可以使用这种资源的变量了。

实现PHP中的fopen函数:

[cpp]  view plain copy
  1. PHP_FUNCTION(my_fopen)  
  2. {  
  3.     zval *res;  
  4.   
  5.     char *filename, *mode;  
  6.       
  7.     int filename_strlen, mode_strlen;  
  8.   
  9.     FILE *fp;  
  10.       
  11.     if(zend_parse_parameters(ZEND_NUM_ARGS TSRMLS_CC, "s|s",  &filename, &filename_strlen, &mode, &mode_strlen) == FAILURE){  
  12.         RETURN_FALSE;  
  13.     }  
  14.   
  15.     //此处省略了对参数的有效性验证  
  16.     fp = fopen(filename, mode);  
  17.   
  18.     ZEND_REGISTER_RESOURCE(res, fp, my_resource_descriptor);//向全局变量&EG(regular_list)中注册资源变量,并将对应HashTable的ID赋值给res  
  19.   
  20.     RETURN_RESOURCE(res);//向PHP返回资源变量  
  21. }  
这里,定义了PHP中名称为my_fopen的函数。my_fopen(string $file_name, string $mode)


实现PHP中的fclose函数:

[cpp]  view plain copy
  1. PHP_FUNCTION(my_fclose)  
  2. {  
  3.     zval *res;  
  4.       
  5.     FILE *fp;  
  6.   
  7.     if(zend_parse_parameters(ZEND_NUM_ARGS TSRMS_CC, "r", &res) == FAILURE){  
  8.         RETURN_FALSE;  
  9.     }  
  10.   
  11.     if(Z_TYPE_P(res) == IS_RESOURCE){//判断变量类型是否是资源类型  
  12.         zend_hash_index_del(&EG(regular_list), Z_RESVAL_P(res));//EG就类似于PHP中的$_GLOBALS。在全局资源变量regular_list中删除对应ID的资源  
  13.     }else{  
  14.         php_error_docref(NULL TSRMLS_CC, E_WARNING, "参数必须是资源类型变量");  
  15.         RETURN_FALSE;  
  16.     }  
  17.   
  18.     RETURN_TRUE;  
  19. }  
定义了PHP中名称为my_fclose的函数。my_fclose($resource)


五、编译、安装扩展,重启php-fpm或mod_php5等


六、PHP中使用自定义扩展中的方法

[php]  view plain copy
  1. my_fwrite($fp"aaTest");  
  2.   
  3. var_dump($fp);  
  4.   
  5. my_fclose($fp);  
  6.   
  7. var_dump($fp);  
可以正常,打开和关闭资源。


七、我们在PHP中经常使用数据库连接资源、文件句柄资源,但他们通常无需我们手工释放,也不会出现内存泄漏问题,这是如何实现的呢?

[cpp]  view plain copy
  1. my_resource_descriptor = zend_register_list_destructors_ex(NULL, NULL, MY_RES_NAME, module_number);//向内核中注册新的资源类型  
回到最开始的注册资源类型,看到zend_register_list_destructors_ex的第一个参数,这个参数就是析构函数的指针。

那么,如果需要实现自动释放功能,只需要定义析构函数并传递函数指针即可。


再看一个问题:

[php]  view plain copy
  1. $fp = fopen("test.txt""rw");  
  2.   
  3. var_dump($fp);  
  4.   
  5. //fclose($fp); 此处不使用fclose释放资源  
  6.   
  7. unset($fp); //而是使用unset释放  
  8. //unset没有问题,会正常释放$fp变量。但$fp对应真正的打开文件资源句柄资源将永远释放不了,直至mod_php5或php-fpm重启  
  9. //可以看出,在注册资源类型时定义析构函数的必要性了  

定义析构函数:

[cpp]  view plain copy
  1. static void php_myres_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC){//析构函数被调用时,会接受一个当前资源变量的参数  
  2.     FILE *fp = (FILE*)rsrc->ptr;  
  3.     fclose(fp);  
  4. }  
  5.   
  6. ZEND_MINIT_FUNCTION(jinyong)  
  7. {  
  8.     my_resource_descriptor = zend_register_list_destructors_ex(php_myres_dtor, NULL, MY_RES_NAME, module_number);  
  9. }  
[cpp]  view plain copy
  1. 在PHP中,所谓资源变量,实际都是通过存储整型值,在到内核全局资源变量列表EG(regular_list)中找到对应的指针,并进行相应操作。  
  2. 资源类型是一种特殊的变量,保存了到外部资源的一个引用。资源是通过专门的函数来建立和使用的。

    比如数据库连接,打开文件,图形画布区域等。

    资源类型其实仅仅是一个整数,而内核可以根据这个整数值去一个类似资源池的地方寻找最终需要的数据。

    例1,文件操作的例子:
     

    代码示例:

    $file=fopen('a.txt','r');//使用fopen函数打开一个文件获取句柄。  
    fread($file,1024);//之后把该句柄传递给fread函数,即可对此文件进行后续操作。

    例2,数据库操作的例子:
     

    代码示例:

    $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)