本文将简单介绍资源类型、资源的创建、访问、销毁操作。不再建议使用资源类型,使用类更为合适
资源类型 zval
可以表示大部分的PHP数据类型,但有一样不能很好的表示其结构:指针。由于不透明的结构、无法使用传统运算符进行操作等,使得指针在PHP的表示变得困难。因此PHP用一个特殊的标记表示指针:资源,为了使资源标记具有意义,必须先注册到zend engine才能使用。
在头文件定义结构体php_test_person
以及资源名称,放置在#define
语句后,PHP_MINIT_FUNCTION(test);
之前
1 2 3 4 5 6 typedef struct { zend_string *name; zend_long age; } php_test_person; #define PHP_TEST_PERSON_RES_NAME "Person Data"
源文件定义le_*
全局变量,MINIT阶段注册,用于获取资源类型、字面意义名称、析构函数
1 2 3 4 5 6 int le_test_person;PHP_MINIT_FUNCTION(test) { le_test_person = zend_register_list_destructors_ex(NULL , NULL , PHP_TEST_PERSON_RES_NAME, module_number); }
初始化资源 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 PHP_FUNCTION(test_person_new) { php_test_person * person; zend_string * name; zend_long age; ZEND_PARSE_PARAMETERS_START(2 ,2 ) Z_PARAM_STR(name); Z_PARAM_LONG(age); ZEND_PARSE_PARAMETERS_END(); if (age < 0 || age > 255 ) { php_error_docref(NULL , E_WARNING, "Nonsense age (%ld) given, person resource not created." , age); RETURN_FALSE; } person = emalloc(sizeof (php_test_person)); person->name = zend_string_copy(name); person->age = age; RETURN_RES(zend_register_resource(person, le_test_person)); }
函数接收参数name以及age,参数进行校验通过后,申请一段内存空间并写入数据,return_value
返回该资源。PHP不需要知道资源的具体内部表示,只需要获取该资源存储的指针以及资源类型
函数接收资源参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 PHP_FUNCTION(test_person_greet) { php_test_person *person; zval *zperson; ZEND_PARSE_PARAMETERS_START(1 ,1 ) Z_PARAM_RESOURCE(zperson); ZEND_PARSE_PARAMETERS_END(); person = (php_test_person *) zend_fetch_resource_ex(zperson, PHP_TEST_PERSON_RES_NAME, le_test_person); php_printf("Hello " ); PHPWRITE(ZSTR_VAL(person->name), ZSTR_LEN(person->name)); php_printf("!\nAccording to my records, you are %ld years old.\n" , person->age); RETURN_TRUE; }
ZEND_FETCH_RESOURCE
在PHP7中已被删除,目前获取资源的函数是zend_fetch_resource
、zend_fetch_resource_ex
。zend_fetch_resource_ex
函数需要一个zval、字面意义名称、资源类型,返回一个指针。函数内部切记不要free
该指针
销毁资源 在PHP中使用fopen
打开文件并获得一个资源句柄$fp
,接下来unset($fp)
时文件被关闭,即使没有使用fclose
函数。其中的奥秘在zend_register_list_destructors_ex
,该函数第一个参数为常规资源的析构函数,第二个为持久化资源的析构函数,当离开资源变量所在作用域时,自动调用清理/析构函数,释放内存、关闭连接或执行其他清理操作
1 2 3 4 5 6 7 8 9 10 11 12 13 le_test_person = zend_register_list_destructors_ex(php_test_person_dtor, NULL , PHP_TEST_PERSON_RES_NAME, module_number); static void php_test_person_dtor (zend_resource *res) { php_test_person *person = (php_test_person *) res->ptr; if (person) { if (person->name) { zend_string_release(person->name); } efree(person); } }
强制销毁资源 使用zend_list_delete
销毁资源,该函数可销毁任何资源类型变量
1 2 3 4 5 6 7 8 9 10 11 12 PHP_FUNCTION(test_person_delete) { zval * zperson; ZEND_PARSE_PARAMETERS_START(1 ,1 ) Z_PARAM_RESOURCE(zperson); ZEND_PARSE_PARAMETERS_END(); zend_list_delete(Z_RES_P(zperson)); RETURN_TRUE; }
持久化资源 持久化资源与常规资源不同的地方在析构函数声明注册的位置,数据内存申请使用pemalloc
代替emalloc
1 2 3 4 5 6 7 int le_test_person_persist;PHP_MINIT_FUNCTION(test) { le_test_person = zend_register_list_destructors_ex(php_test_person_dtor, NULL , PHP_TEST_PERSON_RES_NAME, module_number); le_test_person_persist = zend_register_list_destructors_ex(NULL , php_test_person_persist_dtor, PHP_TEST_PERSON_RES_NAME, module_number); }
通常情况下,php_test_person_dtor
会在请求结束后调用,php_test_person_persist_dtor
在扩展shutdown阶段调用
1 2 3 4 5 6 7 8 9 10 11 static void php_test_person_persist_dtor (zend_resource *res) { php_test_person *person = (php_test_person *) res->ptr; if (person) { if (person->name) { zend_string_release(person->name); } pefree(person, 1 ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 PHP_FUNCTION(test_person_pnew) { php_test_person * person; zend_string * name; zend_long age; ZEND_PARSE_PARAMETERS_START(2 ,2 ) Z_PARAM_STR(name); Z_PARAM_LONG(age); ZEND_PARSE_PARAMETERS_END(); if (age < 0 || age > 255 ) { php_error_docref(NULL , E_WARNING, "Nonsense age (%ld) given, person resource not created." , age); RETURN_FALSE; } person = pemalloc(sizeof (php_test_person), 1 ); person->name = zend_string_dup(name, 1 ); person->age = age; RETURN_RES(zend_register_resource(person, le_test_person_persist)); }
test_person_pnew
与test_person_new
仅在数据初始化、资源类型方面有差异
查找已存在的持久化资源 为了可以重用持久化资源,需要将其保存在一个安全的地方,zend engine提供了一个executor global通过EG(persistent_list)
访问,该变量类型为HashTable
重新修改test_person_pnew
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 PHP_FUNCTION(test_person_pnew2) { php_test_person * person; char * key_raw; size_t key_len; zend_string * name, * key; zend_long age; zval * zperson = NULL ; ZEND_PARSE_PARAMETERS_START(2 ,2 ) Z_PARAM_STR(name); Z_PARAM_LONG(age); ZEND_PARSE_PARAMETERS_END(); if (age < 0 || age > 255 ) { php_error_docref(NULL , E_WARNING, "Nonsense age (%ld) given, person resource not created." , age); RETURN_FALSE; } key_len = spprintf(&key_raw, 0 , "test_person_%s_%ld\n" , ZSTR_VAL(name), age); key = zend_string_init(key_raw, key_len, 1 ); efree(key_raw); if ((zperson = zend_hash_find(&EG(persistent_list), key)) != NULL ) { person = (php_test_person *) zend_fetch_resource_ex(zperson, PHP_TEST_PERSON_RES_NAME, le_test_person_persist); ZVAL_RES(return_value, zend_register_persistent_resource_ex(key, person, le_test_person_persist)); zend_string_release(key); return ; } person = pemalloc(sizeof (php_test_person), 1 ); person->name = zend_string_copy(name); person->age = age; ZVAL_RES(return_value, zend_register_persistent_resource_ex(key, person, le_test_person_persist)); zend_string_release(key); }
test_person_pnew2
先确定EG(persistent_list)
是否已经存在,已存在则直接使用,不存在则申请内存初始化资源
参考文档 Extension Writing Part III: Resources Extension Writing Part III: Resources Upgrading PHP extensions from PHP5 to NG PHP Internals Book References about Maintaining and Extending PHP