Теперь самое время приступить к созданию переменных, которые потом будут доступны в пользовательском пространстве PHP. Процесс создания переменных не сложнее процесса получения данных хранимых в переменных, который был рассмотрен в предыдущих разделах.
Процесс создания можно разделить на два этапа. Первый этап подготовка данных переменной. Второй этап связывание данных с символическим именем переменной и регистрация нужной таблице символов. Теперь обо всем по порядку.
Первым делом необходимо подготовить данные. Так как данные в Zend Engine
представляют из себя структуру zval
, то под эту структуру необходимо выделить блок памяти и присвоить указатель на выделенный блок переменной типа zval
.
Для выделения памяти под структуру zval
необходимо использовать макрос MAKE_STD_ZVAL()
. Данный макрос выделяет необходимый блок памяти, автоматически проверяет ошибки связанные нехваткой свободной памяти, а также инициализирует должным образом счетчик ссылок(refcount__gc
и is_ref__gc
), с помощью макрос INIT_PZVAL()
. В качестве аргумента макрос принимает указатель на zval
zval *var = NULL;
MAKE_STD_ZVAL(var);
Так же для выделения памяти существует еще один макрос ALLOC_INIT_ZVAL()
, который отличается от макроса MAKE_STD_ZVAL()
только тем, что он инициализирует данные значением NULL
.
После того как память под структуру zval
выделена в ней необходимо сохранить данные нужного нам типа. Для инициализации структуры zval
Zend Engine предоставляет специальный набор макросов ZVAL_
.
ZVAL_NULL(zval *)
– инициализирует структуру значением NULL
, в качестве аргумента принимает указатель на структуру zval
.
Код:
zval *var = NULL;
MAKE_STD_ZVAL(var);
ZVAL_NULL(var);
Эквивалентен:
zval *var = NULL;
ALLOC_INIT_ZVAL(var);
ZVAL_BOOL(zval *, b)
– инициализирует структуру булевым значением. Первым аргументом принимает указатель на структуру zval
, вторым аргументом значение 1 или 0. Если в качестве значения передаётся 1 (или любое другое значение отличное от 0), то переменная будет иметь булево значение TRUE
, иначе FALSE
. Также существует еще два макроса для инициализации переменной булевым значением: ZVAL_FALSE(zval *)
– эквивалент ZVAL_BOOL(zval *, 0)
и ZVAL_TRUE(zval *)
– эквивалент ZVAL_BOOL(zval *, 1)
ZVAL_LONG(zval *, l)
– инициализирует структуру целочисленным значением. Первым аргументом принимает указатель на структуру zval
, вторым аргументом целочисленное значение.
ZVAL_DOUBLE(zval *, d)
– инициализирует структуру значением c плавающей запятой. Первым аргументом принимает указатель на структуру zval
, вторым аргументом значение c плавающей запятой, соответственно.
ZVAL_STRINGL(zval *, str, len, dup)
– инициализирует структуру строковым значением. Первым аргументом принимает указатель на структуру zval
, вторым аргументом строковые данные, третьим аргументом длину строки, четвертым аргументом флаг копирования строки. Если четвертый аргумент имеет значение 1, то под строку будет выделена новая область памяти и содержимое строки будет в нее скопировано, иначе переменная будет просто связана с уже существующими строковыми данными, в zval
будет сохранен указатель на существующие строковые данные.
Также для инициализации структуры строковыми данными существует еще один макрос ZVAL_STRING(zval *, str, dup)
, данный макрос отличается от предыдущего только тем, что не принимает длину строки в качестве аргумента. Макрос ZVAL_STRINGL()
безопасен при работе с двоичными данными.
Для создания пустых строк существует макрос ZVAL_EMPTY_STRING(zval *)
_ZVAL_RESOURCE(zval *, res)
– инициализирует структуру значением номера ресурса. Первым аргументом принимает указатель на структуру zval
, вторым аргументом целочисленный номер ресурса
Примеры создания и инициализации структуры zval
// Создание переменной со значением NULL
zval *var = NULL;
MAKE_STD_ZVAL(var);
ZVAL_NULL(var);
// Создание целочисленной переменной со значением 150
zval *var = NULL;
MAKE_STD_ZVAL(var);
ZVAL_LONG(var, 150);
// Создание строковой переменной
zval *var = NULL;
MAKE_STD_ZVAL(var);
ZVAL_STRING(var, "String variable", 1);
// Создание пустой строковой переменной
zval *var = NULL;
MAKE_STD_ZVAL(var);
ZVAL_EMPTY_STRING(var);
Использованием макросов ZVAL_
- не единственный способ инициализации структуру zval
, но этот способ самый простой и предпочтительный.
Инициализировать структуру можно с использованием уже известных макросов семейства Z_TYPE_*
и Z_*VAL_*
:
// Создание переменной со значением NULL
zval *var = NULL;
MAKE_STD_ZVAL(var);
Z_TYPE_P(var) = IS_NULL;
// Создание целочисленной переменной со значением 150
zval *var = NULL;
MAKE_STD_ZVAL(var);
Z_TYPE_P(var) = IS_LONG;
Z_LVAL_P(var) = 150;
// Создание строковой переменной
zval *var = NULL;
MAKE_STD_ZVAL(var);
Z_TYPE_P(var) = IS_STRING;
Z_STRLEN_P(val) = strlen("String variable");
Z_STRVAL_P(val) = estrndup("String variable", strlen("String variable") + 1);
// Создание пустой строковой переменной
zval *var = NULL;
MAKE_STD_ZVAL(var);
Z_TYPE_P(var) = IS_STRING;
Z_STRLEN_P(val) = 0;
Z_STRVAL_P(val) = STR_EMPTY_ALLOC();
Еще один способ инициализации - это установка вручную значения соответствующих элементов структуры zval
:
// Создание переменной со значением NULL
zval *var = NULL;
MAKE_STD_ZVAL(var);
new_long->type = IS_NULL;
// Создание целочисленной переменной со значением 150
zval *var = NULL;
MAKE_STD_ZVAL(var);
new_long->type = IS_LONG;
new_long->value.lval = 10;
// Создание строковой переменной
zval *var = NULL;
MAKE_STD_ZVAL(var);
var->type = IS_STRING;
var->value.str.len = strlen("String variable");
var->value.str.val = estrdup("String variable", strlen("String variable") + 1 );
// Создание пустой строковой переменной
zval *var = NULL;
MAKE_STD_ZVAL(var);
var->type = IS_STRING;
var->value.str.len = 0;
var->value.str.val = STR_EMPTY_ALLOC();
Последний способ инициализации переменных вручную приведен для ознакомления и недолжен использоваться на практике, так как это не гарантирует сохранение совместимости с будущими версиями PHP.
Теперь когда данные подготовлены их необходимо ассоциировать с символьным названием переменной под которым она будет доступна в пользовательском пространстве PHP
.
Таблицы символов
Любая переменная пользовательского пространства PHP храниться во внутреннем контейнере Zend Engine
. Когда вы создаете переменную в пользовательском пространстве PHP и задаете ей название, Zend Engine
сохраняет это название в этом контейнере, который также известен как таблица символов (symbol table
). В Zend Engine
существует два типа таблиц символов. Первый тип - это глобальная таблица символов, которая инициализируется до вызова метода PHP_RINIT()
и уничтожается перед вызовом метода PHP_RSHUTDOWN()
. Переменные зарегистрированные в этой таблице доступны в любом месте сценария.
В момент когда начинает выполняется функция или метод объекта, в пользовательском пространстве PHP
, Zend Engine
создает новую таблицу символов, локальную, которая существует пока выполняется эта функция или метод. Переменные зарегистрированные в данной таблице символов являются локальными и доступны только в области видимости выполняемой функции или метода. Эта таблица объявляется активной. Таблица является активной пока выполняется функция или метод и все переменные создаваемые в этот момент Zend Engine
помещает именно в эту таблицу символов.
Если в текущий момент времени выполнение скрипта не находиться в функции или методе, то активной считается глобальная таблица символов.
В Zend Engine
таблицы символов являются элементами глобальной структуры zend_executor_globals
. Оба элемента имеют тип HashTable
. Глобальная таблица символов храниться в элементе symbol_table
, а элемент active_symbol_table
является указателем на активную в данный момент таблицу символов:
struct _zend_executor_globals {
HashTable symbol_table; // Глобальная таблица символов
HashTable *active_symbol_table; // Активная (локальная) таблица символов
...
};
Для доступа к таблицам символов Zend Engine
предоставляет макрос EG()
(Executor Globals). Необходимо отметить, что данный макрос служит для доступа не только к таблицам символов, но и к другим элементам глобальной структуры zend_execution_globals
. В качестве параметра макрос принимает название элемента глобальной структуры.
Чтобы наша переменная стала доступна в пользовательском пространстве, ее нужно зарегистрировать либо в активной в данный момент таблице символов или в глобальной таблице символов, используя макрос ZND_SET_SYMBOL(table, name, zval *)
. Первым аргументом в макрос передается таблица в которой будет зарегистрирована переменная, вторым аргументом - символическое имя переменной под которым она будет доступна в пользовательском пространстве, третьим - указатель на структуру с данными.
Вызов ZEND_SET_SYMBOL()
заносит переменную в таблицу символов перед этим осуществляется проверка существует ли уже переменная с данной меткой в таблице символов и автоматически уничтожает данные, если указатель на них уже есть в таблице символов. Это наиболее предпочтительный способ, если скорость не является критичной и/или необходимо ограничить размер используемой памяти.
// Создание строковой переменной
zval *var = NULL;
MAKE_STD_ZVAL(var);
var->type = IS_STRING;
var->value.str.len = strlen("String variable");
var->value.str.val = estrdup("String variable", strlen("String variable") + 1 );
// Связывание данных с меткой переменной
ZEND_SET_SYMBOL(EG(active_symbol_table), "my_str_var", var);
Данный способ является не самым оптимальным с точки зрения производительности и скорости, так как перед регистрацией переменной в таблице символов осуществляется ее поиск в этой таблице.
Если необходима более высокая производительность, а оптимальное использование памяти не является критичным моментом или вы уверены, что переменная с такой меткой не существует в таблице символов, то можно пропустить проверку на существование переменной и форсировать вставку данных в таблицу символов используя zend_hash_update()
:
zval *var = NULL;
MAKE_STD_ZVAL(var);
var->type = IS_STRING;
var->value.str.len = strlen("String variable");
var->value.str.val = estrdup("String variable", strlen("String variable") + 1 );
zend_hash_update( EG(active_symbol_table), "my_str_var",
strlen("my_str_var") + 1, &var, sizeof(zval *), NULL);
Для полноты картины приведу объявление нескольких макросов из заголовочного файла zend_API.h
PHP 5.3.5:
#define ZVAL_LONG(z, l) { \
Z_TYPE_P(z) = IS_LONG; \
Z_LVAL_P(z) = l; \
}
#define ZVAL_STRING(z, s, duplicate) { \
const char *__s=(s); \
Z_STRLEN_P(z) = strlen(__s); \
Z_STRVAL_P(z) =( duplicate ? estrndup(__s, \
Z_STRLEN_P(z)):(char*)__s);\
Z_TYPE_P(z) = IS_STRING; \
}
#define ZVAL_EMPTY_STRING(z) { \
Z_STRLEN_P(z) = 0; \
Z_STRVAL_P(z) = STR_EMPTY_ALLOC();\
Z_TYPE_P(z) = IS_STRING; \
}
Копирование/разделение данных
Так как контейнер zval
представляет из себя сложную структуру, в которой хранятся данные разных типов и указатели на внешние данные, например, хэш-таблицы или объекты, которые в свою очередь также могут иметь вложенные указатели на другие данные, то для копирования одной структуры zval
в другую без потери данных и связей необходимо использовать специальный конструктор копирования:
void zval_copy_ctor(zval *zvalue)
Конструктор копирования работает непосредственно с переданными данными через парметр zvalue
:
zval *dst;
tmp = *src;
MAKE_STD_ZVAL(src);
Z_TYPE_P(src) = IS_LONG;
Z_LVAL_P(src) = 150;
dst = src;
zval_copy_ctor(dst);
INIT_PZVAL(dst);
При использовании конструктора копирования счетчик ссылок (refcount__gc
) и флаг (is_ref__gc
), указывающий на то является ли данная структура ссылкой на другие данные, не изменяются. Т.е если предположить, что в примере на данные src
ссылаются другие данные(refcount__gc
> 1) или же src
является ссылкой на другие данные (is_ref__gc
= = 1), после копирования данных в контейнере dst
значения is_ref__gc
и refcount__gc
будут такими же как и в src
. Для сброса счетчика и флага в примере используется макрос INIT_PZVAL()
Если в этом примере опустить вызов конструктора копирования, то dst
и src
будут указывать на одну и туже область памяти, т.е. dst
будет дополнительным указателем на те же самые данные. При изменении данных, на которые указывает src
, dst
также будет изменено.
Еще один пример поясняющий работу конструктора копирования:
PHP_FUNCTION(test_zval_ctor) {
zval *parameter;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", ¶meter) == FAILURE)
return;
}
// Изменяем данные
Z_TYPE_P(parameter) = IS_LONG;
Z_LVAL_P(parameter) = 150;
*return_value = *parameter;
zval_copy_ctor(return_value);
INIT_PZVAL(return_value);
}
Если в этом примере опустить вызов конструктора копирования и если в функцию параметр передается по ссылке, то изменение данных в zval
контейнере parameter
повлечет изменение и внешних данных, ссылка на которые была передана в функцию. Если изменения над переданными в функцию данными должны быть локальными, то вначале необходимо их скопировать.
Помимо конструктора копирования zval_copy_ctor()
в Zend Engine
есть еще несколько макросов копирования/разделения данных, объявленых в Zend/zend.h
:
SEPARATE_ZVAL(zval **ppzv)
- дублирует данные во временную структуру, является оберткой над конструктором копирования zval_copy_ctor()
. При использовании данного макроса копирование данных во временную структуру произойдет только когда счетчик ссылок refcount__gc
больше 1. Т.е макрос разделяет zval
контейнеры. После разделения счетчик ссылок refcount__gc
и флаг is_ref__gc
временной структуре будет инициализированы значением 1 и 0 соответственно, а счетчик ссылок контейнера ppzv
будет уменьшен на еденицу
SEPARATE_ZVAL_IF_NOT_REF(zval **ppzv)
- данный макрос выполняет ту же функцию, что и SEPARATE_ZVAL()
с тем отличием, что zval
контейнеры будут разделены только тогда, когда они не являются ссылкой на другие данные, т.е. is_ref__gc
== 0
SEPARATE_ZVAL_TO_MAKE_IS_REF(zval **ppzv)
- данный макрос выполняет ту же функцию, что и SEPARATE_ZVAL_IF_NOT_REF()
, но после копирования/разделения устанавливает флаг is_ref__gc
в значение 1, т.е делает временную структуру ссылкой
MAKE_COPY_ZVAL(zval **ppzv, zval *pzv)
- данный макрос доступен начиная с версии PHP 5.3.5 и является оберткой над zval_copy_ctor()
. После копирования данных во временную структуру вызывает макрос INIT_PZVAL(pzv)
, который устанавливает is_ref__gc
в значение 0, а элемент refcount__gc
в значение 1. Использование данного макроса эквивалентно:
dst = **src;
zval_copy_ctor(dst);
INIT_PZVAL(dst);
Уничтожение данных
Когда счетчик ссылок достигнет нуля, внутренние структуры контейнера zval
будут уничтожены автоматически с помощью вызова деструктора zval_dtor(zval *zval_ptr)
, после чего будет вызвана функция efree(*zval_ptr)
для уничтожения самого zval
контейнера
zval_dtor(zval *zval_ptr)
- деструктор данных
При вызове деструктора данных, происходит освобождение внутренних структур которые связанны с zval
контейнером zval_ptr
. К таким структурам относятся массивы (хэш-таблицы), объекты, ресурсы.
Иногда бывает необходимо уничтожить данные "вручную", такая потребность часто возникает при использовании временных zval
контейнеров внутри функций, которые не возвращаются из функций как результат. Для этого нужно вызвать zval_dtor()
, а затем, для уничтожения самого zval-контейнера - FREE_ZVAL(zval *zval_ptr)
:
zval *var;
/* Выделяем память и инициализируем zval контейнер */
MAKE_STD_ZVAL(var);
ZVAL_STRING(var, "my string", 1);
/* .... */
zval_dtor(var);
FREE_ZVAL(var);
Если необходимо уничтожить переменную зарегистрированную в таблице символов, то вместо zval_dtor()
необходимо воспользоваться функцией zend_hash_del()
:
zval *var;
/* Выделяем память и инициализируем zval контейнер */
MAKE_STD_ZVAL(var);
ZVAL_STRING(var, "my string", 1);
ZEND_SET_SYMBOL(EG(active_symbol_table), "var", var);
/* .... */
zend_hash_del( EG(active_symbol_table), "var", sizeof("var"));
FREE_ZVAL(var);
);