Ресурсы в PHP - это специальный, абстрактный тип в котором можно хранить любые пользовательские данные. К таким данным можно отнести дескрипторы файлов, структуры данных, различные хэндлы и т.д.
У каждого ресурса есть уникальный идентификатор - простое цело число, которое храниться в структуре zval
и используется как индекс внутренней хэш-таблицы (см. главу "Хэш-таблицы"), в которой храниться указатель на зарегистрированные пользовательских данные.
Ресурсы автоматически уничтожаются, когда счетчик ссылок достигает 0 (см. главу "Данные и переменные"), при уничтожении вызывается функция-деструктор, если она была указана.
Ресурсы разделяются на два типа: простые и постоянные. Простые ресурсы - существуют в пределах обработки одного запроса и уничтожаются в конце сессии. Постоянные ресурсы - существуют между запросами и уничтожаются при выгрузке модуля.
Регистрация ресурса
Регистрацию ресурса можно разделить на несколько простых действий:
- Объявление глобальной переменной, в которой будет храниться идентификатор типа ресурса
- Объявление человекопонятного названия типа ресурса
- Написание функций-деструкторов ресурса
- Регистрация типа ресурса
- Регистрация ресурса
Как уже было сказано у ресурса есть уникальный идентификатор, помимо это идентификатора у ресурса есть идентификатор типа, который представляет из себя простое цело число. В расширениях, идентификатор типа должен храниться в глобальной переменной с типом int
. Принято, что такие переменные имею префикс le_
. Объявления переменной, хранящей тип ресурса, необходимо выполнить в самом начале, перед объявлением функций:
static int le_my_resource;
Помимо идентификатора типа, ресурс также может иметь человекопонятное название типа, которое является константным значением. Название типа ресурса можно определить в заголовочном файле (или в файле с кодом) расширения следующим образом:
#define PHP_MY_RESOURCE_NAME "My resource"
Данное имя является не обязательным и используется для того, чтобы сделать пользовательские вывод информации/сообщений об ошибках более понятными.
Первым делом необходимо зарегистрировать свой тип ресурса с помощью одной из из следующих функций:
int zend_register_list_destructors(void (*ld)(void*), void (*pld)(void*), int module_number)
int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char* res_name, int module_number)
Первым параметром функции принимают указатель на функцию-деструктор, которая будут вызвана при уничтожении обычного ресурса. Вторым - указатель на функцию-деструктор, которая будут вызвана при уничтожении постоянного ресурса. В качестве первого и/или второго аргумента допустимо использование NULL
, если нет необходимости в функциях-деструкторах. Последним аргументом обе функции принимают идентификатор модуля.
Вторая функция, в качестве третьего аргумента, принимает человекопонятное название ресурса.
Необходимо обратить внимание, на то, что функции принимают указатели на функции-деструкторы разного типа. Первая функция принимает указатель на void
функцию-деструктор, которая не принимает никаких аргументов. Вторая функция принимает на функцию функцию-деструктор со следующим прототипом:
typedef void (*rsrc_dtor_func_t)(zend_rsrc_list_entry *rsrc TSRMLS_DC)
При вызове функции в качестве единственного аргумента в нее будет передан указатель на структуру zend_rsrc_list_entry
:
typedef struct _zend_rsrc_list_entry {
void *ptr; /* Указатель на данные */
int type; /* Идентификатор типа ресурса */
int refcount; /* Кол-во ссылок */
} zend_rsrc_list_entr
Функции возвращают идентификатор типа ресурса, который необходимо присвоить глобальной переменной, объявленной в самом начале. В дальнейшем этот идентификатор будет использоваться при непосредственной регистрации ресурса, а также поиске/получении ресурса по его идентификатору.
Регистрация типа ресурса должна осуществляться только один раз, в функции PHP_MINIT_FUNCTION()
- функция вызывается единожды, при загрузке модуля.
Примеры регистрации типа ресурса:
#define PHP_MY_RESOURCE_NAME "My resource"
/*…*/
static int le_my_resource;
static void php_my_res_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC) {
FILE *fp = (FILE*)rsrc->ptr;
fclose(fp);
}
PHP_MINIT_FUNCTION(test_module) {
/*…*/
le_my_resource = zend_register_list_destructors_ex(php_my_res_dtor, NULL, PHP_MY_RESOURCE_NAME, module_number);
return SUCCESS;
}
После того как зарегистрирован тип ресурса и получен его уникальный идентификатор, пришло самое время зарегистрировать ресурс. Регистрация ресурса осуществляется с помощью макроса
ZEND_REGISTER_RESOURCE(zval *rsrc, void *ptr, int type)
- осуществляет регистрацию ресурса и возвращает его уникальный идентификатор.
Первым аргументом макрос принимает указатель на инициализированный zval
-контейнер. Вторым аргументом - указатель на наши данные. Третьим - уникальный идентификатор типа ресурса, который мы получили и сохранили в глобальной переменной, когда регистрировали функции-деструкторы.
По сути данный макрос является синонимом к функции zend_register_resource(zval *rsrc_result, void *rsrc_pointer, int rsrc_type)
:
#define ZEND_REGISTER_RESOURCE(rsrc_result, rsrc_pointer, rsrc_type) \
zend_register_resource(rsrc_result, rsrc_pointer, rsrc_type);
Когда происходит регистрация ресурса, указатель на пользовательские данные rsrc_pointer
и идентификатор типа ресурса rsrc_type
помещаются в хэш-таблицу regular_list
, которая расположена в глобальной таблице executor_globals
(EG(regular_list
). С помощью функции zend_hash_next_free_element()
, функция возвращает идентификатор элемента хэш-таблицы, который заноситься в переменную rsrc_result
, если указатель на нее был передан:
if (rsrc_result) {
rsrc_result->value.lval = rsrc_id;
rsrc_result->type = IS_RESOURCE;
}
Примеры регистрации ресурса:
PHP_FUNCTION(test_fopen)
{
FILE *fp = NULL;
char *fname = NULL;
char *mode = NULL;
int fname_len = 0;
int mode_len = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &fname;, &fname;_len, &mode;, &mode;_len) == FAILURE) {
RETURN_FALSE;
}
if (!fname_len || !mode_len) {
zend_error(E_WARNING, "Invalid filename or mode");
RETURN_FALSE;
}
fp = fopen(fname, mode);
if (NULL == fp) {
RETURN_FALSE;
}
ZEND_REGISTER_RESOURCE(return_value, fp, le_my_resource);
}
Если нет необходимости в том, чтобы макрос ZEND_REGISTER_RESOURCE()
заносил идентификатор в zval
-контейнер, то в качестве значения первого параметра необходимо передать NULL
. Предыдущий пример можно переписать следующим, эквивалентным образом:
PHP_FUNCTION(test_fopen)
{
FILE *fp = NULL;
char *fname = NULL;
char *mode = NULL;
int fname_len = 0;
int mode_len = 0;
int rsrc_id = -1;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &fname;, &fname;_len, &mode;, &mode;_len) == FAILURE) {
RETURN_FALSE;
}
if (!fname_len || !mode_len) {
zend_error(E_WARNING, "Invalid filename or mode");
RETURN_FALSE;
}
fp = fopen(fname, mode);
if (NULL == fp) {
RETURN_FALSE;
}
rsrc_id = ZEND_REGISTER_RESOURCE(NULL, fp, le_my_resource);
/* some code */
RETURN_RESOURCE(rsrc_id)
}
Выполнение следующего кода:
$file_res = test_fopen("test_file.txt", "r");
var_dump($file_res);
выведет на экран:
resource(1) of type (My resource)
Получение ресурса
void *zend_list_find(int rsrc_id, int *found_rsrc_type TSRMLS_DC)
- функция осуществляет поиск ресурса. Первым аргументом функция принимает идентификатор ресурса. Если в качестве второго аргумента передан указатель на переменную, то в эту переменную будет записан идентификатор типа найденного ресурса. Функция возвращает NULL, если ресурс небыл найден, иначе указатель на ресурс.
PHP_FUNCTION(test_fwrite) {
zval *res = NULL;
int rsrc_type = -1;
char *data = NULL;
int data_len = 0;
FILE *fp = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &res;, &data;, &data;_len) == FAILURE) {
RETURN_FALSE
}
fp = (FILE *) zend_list_find(Z_RESVAL_P(res), &rsrc;_type TSRMLS_CC);
if (!fp || rsrc_type != le_my_resource) {
zend_error(E_WARNING, "Invalid resource provided");
RETURN_FALSE
}
if(data_len == fwrite(data, sizeof(char), data_len, fp)) {
RETURN_TRUE
}
RETURN_FALSE
}
Поиск осуществляется в хэш-таблице EG(regular_list)
. Для поиска ресурса в хэш-таблице вызывается функция zend_hash_find()
zend_fetch_resource(zval **rsrc TSRMLS_DC, int rsrc_id, const char *rsrc_name, int *found_rsrc_type, int num_rsrc_types, …)
- функция также как и zend_list_find()
осуществляет поиск ресурса, но в отличие от нее, функция, для поиска, принимает идентификатор ресурса rsrc_id
или zval
-контейнер. Также в функции производятся дополнительные проверки типа найденного ресурса.
Аргументы функции:
Аргумент | Описание |
---|---|
rsrc_zval | контейнер, в котором содержится идентификатор ресурса |
rsrc_id | идентификатор ресурса |
rsrc_name | человекопонятное название ресурса, используется только в сообщениях об ошибках |
found_rsrc_type | указатель на переменную, в которую будет записан идентификатор типа найденного ресурса |
num_rsrc | кол-во идентификаторов типа ресурса |
… | идентификаторы типа ресурса |
Если в функцию, в качестве значения аргумента rsrc
, передать zval
-контейнер, а в качестве значения аргумента rsrc_id
-1, то идентификатор ресурса будет извлечен из zval
-контейнера и поиск будет осуществляться по этому идентификатору. Иначе, поиск будет осуществлен по идентификатору переданному в качестве аргумента rsrc_id
. Если в функцию переданы оба аргумента, то приоритет у rsrc_id
.
Если в функцию в качестве аргумента found_rsrc_type
передан указатель на переменную, то в эту переменную будет записан идентификатор типа найденного ресурса.
Аргумент num_rsrc_types
принимает число - кол-во идентификаторов типа, которые будут переданы после этого аргумента, которым может соответствовать искомый ресурс.
После аргумента num_rsrc_types
в функцию передаются идентификаторы типов, с которыми будет сравниваться идентификатор типа найденного ресурса. Как только будет найдено совпадение, функция завершит свою работу, переменной found_rsrc_type
будет присвоен идентификатор найденного типа.
Внутри функции zend_fetch_resource()
сравнение происходит след. образом:
/* … */
resource = zend_list_find(id, &actual;_resource_type);
/* … */
for (i = 0; i < num_rsrc_types; i++) {
if (actual_resource_type == va_arg(resource_types, int)) {
va_end(resource_types);
if (found_rsrc_type) {
*found_rsrc_type = actual_resource_type;
}
return resource;
}
}
/* … */
Если ресурс не найден или тип найденного ресурса не соответствует не одному заданному типу, функция возвращает NULL
и генерирует предупреждение. Обратите внимание, на то, что предупреждение не будет сгенерировано, если в качестве значения аргумента rsrc_name
будет передан NULL
.
Изменим пример, который был приведен при рассмотрении функции zend_list_find()
:
PHP_FUNCTION(test_fwrite)
{
zval *res = NULL;
int rsrc_type = -1;
char *data = NULL;
int data_len = 0;
FILE *fp = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &res;, &data;, &data;_len) == FAILURE) {
RETURN_FALSE
}
fp = (FILE *) zend_fetch_resource(&res; TSRMLS_CC, -1, PHP_MY_RESOURCE_NAME, NULL, 1, le_my_resource);
if(fp == NULL) {
RETURN_FALSE
}
if(data_len == fwrite(data, sizeof(char), data_len, fp)){
RETURN_TRUE
}
RETURN_FALSE
}
В данном примере вместо функции zend_list_find()
была использована функция zend_fetch_resource()
. Как было сказано в функции производятся проверки типа найденного ресурса с переданными типом, поэтому блок проверки:
if (!fp || rsrc_type != le_my_resource) {
zend_error(E_WARNING, "Invalid resource provided");
RETURN_FALSE
}
излишен.
echo "Test 1<br></br>";
echo "--------------<br></br>";
res = curl_init();
_d($res);
/* Ошибка! */
_d(test_fwrite($res, "Hello, world!\n"));
/* … */
echo "<br></br>Test 2<br></br>";
echo "--------------<br></br>";
$file = test_fopen("/tmp/file.txt", "w+");
var_dump($file);
var_dump(test_fwrite($file, "Hello, world!\n"));
Результат работы скрипта:
Test 1
--------------
resource(2) of type (curl)
Warning: Invalid resource provided in /Users/antonio/Sites/test.php on line 38
bool(false)
Test 2
--------------
resource(3) of type (My resource)
bool(true)
Ниже представлены макросы, обертки над функцией zend_fetch_resource()
:
ZEND_FETCH_RESOURCE(rsrc, resrc_type, passed_id, default_id, resource_type_name, resource_type)
- осуществляет поиск ресурса. Если функция zend_fetch_resource()
вернула NULL
(ресурс не был найден или тип найденного ресурса не соответствует искомому типу resource_type
), функция, в которой был использован макрос, прервется и вернет в пользовательское пространство FALSE
ZEND_FETCH_RESOURCE_NO_RETURN(rsrc, resrc_type, passed_id, default_id, resource_type_name, resource_type)
- аналогичен макросу ZEND_FETCH_RESOURCE()
, но, в отличие от него, не прерывает работу функции, в которой он был использован, если функция zend_fetch_resource()
вернула NULL
Аргументы макросов:
Аргумент | Описание |
---|---|
rsrc | переменная-указатель, которой будет присвоен результат поиска |
resrc_type | тип к которому будет приведен найденный ресурс |
passed_id | zval-контейнер, в котором содержится идентификатор ресурса |
default_id | идентификатор ресурса |
resource_type_name | человекопонятное название ресурса, используется только в сообщениях об ошибках |
resource_type | идентификатор типа искомого ресурса |
Если в функцию, в качестве значения параметра passed_id
, передать zval
-контейнер, а в качестве значения параметра default_id
передать -1, то идентификатор ресурса будет извлечен из zval
-контейнера. Иначе поиск будет осуществлен по идентификатору переданному в качестве аргумента default_id
. Если в макрос переданы оба аргумента, то приоритет у default_id
.
Еще раз изменим нашу функцию testfwrite(), для получения ресурса используем макрос _ZENDFETCHRESOURCE():
PHP_FUNCTION(test_fwrite) {
zval *res = NULL;
int rsrc_type = -1;
char *data = NULL;
int data_len = 0;
FILE *fp = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &res;, &data;, &data;_len) == FAILURE) {
RETURN_FALSE
}
ZEND_FETCH_RESOURCE(fp, FILE *, &res;, -1, PHP_MY_RESOURCE_NAME, le_my_resource);
if(data_len == fwrite(data, sizeof(char), data_len, fp)) {
RETURN_TRUE
}
RETURN_FALSE
}
ZEND_FETCH_RESOURCE2(rsrc, resrc_type, passed_id, default_id, resource_type_name, resource_type1, resource_type2)
ZEND_FETCH_RESOURCE2_NO_RETURN(rsrc, resrc_type, passed_id, default_id, resource_type_name, resource_type1, resource_type2)
Эти макросы идентичным макросам описанным выше за одним лишь исключением - сравнение типа найденного ресурса осуществляется с двумя типами переданными в качестве значений аргументов resource_type
и resource_type2
. Данные макросы используются в функциях, которые могут работать с разными типами ресурсов, например, c обычным и постоянным типом ресурса.
Аргументы макросов:
Аргумент | Описание |
---|---|
rsrc | переменная-указатель, которой будет присвоен результат поиска |
resrc_type | тип к которому будет приведен найденный ресурс |
passed_id | _zval_-контейнер, в котором содержится идентификатор ресурса |
default_id | идентификатор ресурса |
resource_type_name | человекопонятное название ресурса, используется только в сообщениях об ошибках |
resource_type1 | идентификатор типа искомого ресурса |
resource_type2 | идентификатор типа искомого ресурса |
Определение макросов:
#define ZEND_FETCH_RESOURCE(rsrc, rsrc_type, passed_id, default_id, resource_type_name, resource_type) \
rsrc = (rsrc_type) zend_fetch_resource(passed_id TSRMLS_CC, default_id, resource_type_name, NULL, 1, resource_type); \
ZEND_VERIFY_RESOURCE(rsrc);
#define ZEND_FETCH_RESOURCE_NO_RETURN(rsrc, rsrc_type, passed_id, default_id, resource_type_name, resource_type) \
(rsrc = (rsrc_type) zend_fetch_resource(passed_id TSRMLS_CC, default_id, resource_type_name, NULL, 1, resource_type))
#define ZEND_FETCH_RESOURCE2(rsrc, rsrc_type, passed_id, default_id, resource_type_name, resource_type1, resource_type2) \
rsrc = (rsrc_type) zend_fetch_resource(passed_id TSRMLS_CC, default_id, resource_type_name, NULL, 2, resource_type1, resource_type2); \
ZEND_VERIFY_RESOURCE(rsrc);
#define ZEND_FETCH_RESOURCE2_NO_RETURN(rsrc, rsrc_type, passed_id, default_id, resource_type_name, resource_type1, resource_type2) \
(rsrc = (rsrc_type) zend_fetch_resource(passed_id TSRMLS_CC, default_id, resource_type_name, NULL, 2, resource_type1, resource_type2))
Уничтожение ресурса
Как только счетчик ссылок на ресурс достигнет нуля, ресурс будет автоматически уничтожен. Также для уничтожения ресурса можно воспользоваться функцией zend_list_delete(int id)
, которая единственным параметром принимает идентификатор ресурса. Обратите внимание на то, что ресурс может быть удален не сразу. Функция уменьшает кол-во ссылок на ресурс на единицу, после этого, если счетчик ссылок достиг нуля, происходит уничтожения ресурса.
Для уничтожения ресурса вызывается функция-деструктор, если она была зарегистрирована.
Для немедленного удаления ресурса его необходимо из хэш-таблицы EG**(regular_list)
:
PHP_FUNCTION(test_fclose)
{
FILE *fp = NULL;
zval *res = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &res;) == FAILURE) {
RETURN_FALSE;
}
ZEND_FETCH_RESOURCE(fp, FILE *, &res;, -1, PHP_MY_RESOURCE_NAME, le_my_resource);
/* При удалении элемента из хэш таблицы будет вызвана функция-деструктор */
zend_hash_index_del(&EG;(regular_list), Z_RESVAL_P(res));
RETURN_TRUE;
}
Необходимо обратить внимание на то, что функции-деструкторы, если они были указаны при регистрации типа ресурса, будут вызываться всякий раз при уничтожении ресурса, вне зависимости от того как происходит уничтожение - вручную или автоматически.
Другие макросы и функции
int zend_fetch_list_dtor_id(char *type_name)
- возвращает идентификатор типа ресурса по его человекопонятному описанию.
int zend_list_addref(int id TSRMLS_DC)
- увеличивает на единицу счетчик ссылок на ресурс
ZEND_VERIFY_RESOURCE(rsrc)
- проверка значения переменной rsrc
, если значения NULL
, то работа функции, в которой используется макрос, будет прервана и в пользовательское пространство функция вернет FALSE
Определение макроса:
#define ZEND_VERIFY_RESOURCE(rsrc) \
if (!rsrc) { \
RETURN_FALSE; \
}
ZEND_GET_RESOURCE_TYPE_ID(le_id, le_type_name)
- макрос, обертка над функцией zend_fetch_list_dtor_id()
Определение макроса:
#define ZEND_GET_RESOURCE_TYPE_ID(le_id, le_type_name) \
if (le_id == 0) { \
le_id = zend_fetch_list_dtor_id(le_type_name); \
}