Учебник РНР
НазадГлава 34. Создание Переменных Вперёд

Ресурсы

Ресурсы это особый вид данных в PHP. Термин resources\ресурсы означает не к какой-то определённый вид данных, а абстрактный метод обслуживания любого вида информации. Ресурсы хранятся в особом списке ресурсов внутри Zend. Каждое вхождение этого списка имеет соответствующее определение, которое указывает вид ресурса, на который оно ссылается. Zend внутренне обслуживает все ссылки на этот ресурс. Прямой доступ к ресурсу невозможен - это делается только через предоставляемый API. Когда все ссылки на специфический ресурс потеряны, вызывается соответствующая shutdown-функция.

Например, ресурсы используются для хранения ссылок БД и дескрипторов файлов. Стандартная реализация de facto находится в модуле MySQL, но другие модули, такие как Oracle, также используют ресурсы.

Примечание: фактически ресурсом может быть указатель на что-либо, что вам необходимо обработать в функции (например, указатель на структуру), и пользователь должен лишь передать единственную переменную ресурса в вашу функцию.

Чтобы создать новый ресурс, вам необходимо зарегистрировать обработчик уничтожения ресурса. Поскольку вы можете хранить как ресурс любой вид данных, Zend должна знать, как освободить этот ресурс, когда он больше не нужен. Это делается путём регистрации в Zend вашего собственного обработчика уничтожения ресурса/destruction, который, в свою очередь, вызывается Zend-машиной, когда ваш ресурс должен быть освобождён (вручную или автоматически). Регистрация вашего обработчика ресурса в Zend возвращает вам resource type handle\дескриптор типа (этого) ресурса. Этот дескриптор нужен в дальнейшем для обеспечения вашего доступа к ресурсу этого типа и хранится в большинстве случаев в глобальной статической переменной в вашем расширении. Нет необходимости беспокоиться здесь о безопасности потока, поскольку вы регистрируете ваш обработчик ресурса только один раз при инициализации модуля.

Zend-функция для регистрации вашего обработчика ресурса определяется так:
ZEND_API int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld,
    	char *type_name, int module_number);

Есть две разновидности обработчиков уничтожения ресурса, которые вы можете передавать этой функции: обработчик нормальных ресурсов и обработчик постоянных ресурсов. Постоянные ресурсы используются, например, для соединений с базами данных (БД). При регистрации ресурса обязан иметься один из этих обработчиков. Для другого обработчика просто передайте NULL.

zend_register_list_destructors_ex() принимает следующие параметры:
ldВызов обработчика уничтожения нормального ресурса
pldВызов обработчика уничтожения постоянного ресурса
type_nameСтрока, специфицирующая имя вашего ресурса. Хорошо бы всегда специфицировать уникальное имя типа ресурса РНР, чтобы, если пользователь, например, вызовет var_dump($resource);, он получил также имя ресурса.
module_number module_number является доступным автоматически в вашей функции PHP_MINIT_FUNCTION, и, следовательно, вы просто передаёте его.

return-значение это уникальный целочисленный ID (идентификатор) вашего типа ресурса.

Обработчик уничтожения ресурса (нормального или постоянного) имеет следующий прототип:
void resource_destruction_handler(zend_rsrc_list_entry *rsrc TSRMLS_DC);

Передаваемый rsrc это указатель но следующую структуру:

typedef struct _zend_rsrc_list_entry {

    void *ptr;
    int type;
    int refcount;

} zend_rsrc_list_entry;

Член void *ptr это фактический указатель на ваш ресурс.

Теперь мы знаем, как начать: мы определяем наш собственный ресурс, который хотим зарегистрировать в Zend. Это простая структура с двумя целочисленными членами:
typedef struct {

    int resource_link;
    int resource_type;

} my_resource;

Наш обработчик уничтожения ресурса будет, вероятно, выглядеть так:

void my_destruction_handler(zend_rsrc_list_entry *rsrc TSRMLS_DC) {

    // Скорее всего вам придётся привести/cast void-указатель к типу вашей структуры

    my_resource *my_rsrc = (my_resource *) rsrc->ptr;

    // Теперь делайте с вашим ресурсом всё необходимое. Закрывайте
    // Файлы, Сокеты, освобождайте память etc.
    // Также не забудьте реально освободить память вашего ресурса!

    do_whatever_needs_to_be_done_with_the_resource(my_rsrc);
}

Примечание: важно подчеркнуть: Если ваш ресурс это более сложная структура, которая к тому же содержит указатели на память, выделенную в процессе работы, вы должны освободить их перед освобождением самого ресурса!

Теперь, когда мы определили

  1. что такое наш ресурс и

  2. обработчик уничтожения ресурса,

мы можем выполнить остальные шаги:
  1. создать глобальную переменную с расширение, содержащую ID ресурса, чтобы можно было получить к нему доступ из любой функции

  2. определить имя ресурса

  3. написать обработчик уничтожения ресурса

  4. и, наконец, зарегистрировать обработчик

//Где-либо в вашем расширении определите переменную для ваших зарегистрированных ресурсов.
    // Как вам нравится 'le': это просто означает 'list entry/вхождение в списке'.
    static int le_myresource;

    // Определите имя вашего ресурса
    #define le_myresource_name  "My type of resource"

    [...]

    // Теперь определите обработчик уничтожения вашего ресурса
    void my_destruction_handler(zend_rsrc_list_entry *rsrc TSRMLS_DC) {

        my_resource *my_rsrc = (my_resource *) rsrc->ptr;
        do_whatever_needs_to_be_done_with_the_resource(my_rsrc);
    }

    [...]

    PHP_MINIT_FUNCTION(my_extension) {

        // Заметьте, что 'module_number' уже предоставлен посредством
        // определения функции PHP_MINIT_FUNCTION().

le_myresource = zend_register_resource_destructors_ex(my_destruction_handler, NULL, le_myresource_name, module_number);

        // Вы можете регистрировать дополнительные ресурсы, инициализировать ваши
        // глобальные переменные, константы ...
    }

Для регистрации нового ресурса вы можете использовать либо функцию zend_register_resource(), либо макрос ZEND_REGISTER_RESOURE(), определённые в zend_list.h. хотя аргументы для них обоих соответствуют 1:1, лучше всегда использовать макросы для обеспечения совместимости в будущем:
int ZEND_REGISTER_RESOURCE(zval *rsrc_result, void *rsrc_pointer, int rsrc_type);

rsrc_result Это уже инициализированный zval * -контейнер.
rsrc_pointerУказатель на ваш ресурс.
rsrc_typeТип, который вы получили, когда регистрировали обработчик уничтожения ресурса. Если вы следуете схеме именования, это может быть le_myresource.

return-значение это уникальный целочисленный идентификатор данного ресурса.

Когда вы регистрируете новый ресурс, он вставляется во внутренний список Zend, а результат просто хранится в данном zval * -контейнере:
rsrc_id = zend_list_insert(rsrc_pointer, rsrc_type);

    if (rsrc_result) {
        rsrc_result->value.lval = rsrc_id;
        rsrc_result->type = IS_RESOURCE;
    }

    return rsrc_id;

Возвращаемый rsrc_id уникально идентифицирует вновь зарегистрированный ресурс. Вы можете использовать макрос RETURN_RESOURE для возвращения его пользователю:

RETURN_RESOURCE(rsrc_id)

Примечание: обычно, если вы хотите немедленно возвратить ресурс пользователю, вы специфицируете return_value как zval * -контейнер.

Теперь Zend отслеживает все обращения к данному ресурсу. Как только все ссылки на данный ресурс будут утеряны, вызывается обработчик уничтожения ресурса, зарегистрированный ранее для данного ресурса.
Удобно здесь то, что вам не нужно заботиться об утечках памяти, появляющихся при выделении памяти в вашем модуле - просто зарегистрируйте все выделения памяти для вашего вызывающего скрипта как ресурсы. Если скрипт определяет, что эти участки памяти - ресурсы больше ему не нужны, Zend выявит это и сообщит вам.

Получив тот ресурс в некоторой точке, пользователь передаёт его обратно в одну из ваших функций. Контейнер value.lval внутри контейнера zval * содержит ключ/key к вашему ресурсу и, таким образом, может быть использован для получения этого ресурса с помощью макроса ZEND_FETCH_RESOURCE:
ZEND_FETCH_RESOURCE(rsrc, rsrc_type, rsrc_id, default_rsrc_id, resource_type_name, resource_type)

rsrcЭто ваш указатель, указывающий на ваш ранее зарегистрированный ресурс.
rsrc_typeЭто аргумент приведения типа/typecast для вашего указателя,
например, myresource *.
rsrc_idЭто адрес zval * -контейнера, переданный пользователем в вашу функцию, например, g. &z_resource, если дан zval *z_resource.
default_rsrc_id Это целое число специфицирует ID ресурса по умолчанию, если ресурс не может быть получен, или -1.
resource_type_nameЭто имя запрошенного ресурса. Это строка, которая используется, когда ресурс не может быть найден или неверен, для создания сообщения об ошибке.
resource_type resource_type, который вы получили, когда регистрировали обработчик уничтожения ресурса. В нашем примере это был le_myresource.

Этот макрос не имеет return-значения. Это сделано для удобства разработчиков. Он заботится о передаче TSRMLS-аргументов, а также проверяет, может ли ресурс быть получен. Он вызывает появление сообщения-предупреждения и возвращает текущую PHP-функцию с NULL, если была проблема с получением ресурса.

Чтобы форсировать удаление ресурса из списка, используйте функцию zend_list_delete().
Вы можете также форсировать увеличение счётчика ссылок, если знаете, что создаёте другую ссылку на ранее размещённое значение (например, если вы автоматически повторно используете ссылку БД). В этом случае используйте функцию zend_list_addref().
Для поиска ранее выделенных вхождений ресурсов используйте zend_list_find(). Полный API можно найти в zend_list.h.


Назад Оглавление Вперёд
Объекты Вверх Макрос для создания автоматической глобальной переменной