Учебник РНР
Назад Вперёд

Приложение E. Расширение PHP

Содержание
Добавление функций в PHP
Вызов пользовательских функций
Сообщения об ошибках

Добавление функций в PHP

Прототип функции

Все функции выглядят так:
void php3_foo(INTERNAL_FUNCTION_PARAMETERS) {
     
}

Даже если ваша функция не принимает аргументов, вызывается она именно так.

Аргументы функции

Аргументы всегда имеют тип pval. Этот тип содержит объединение/union, которое содержит фактический тип аргумента. Так, если ваша функция принимает два аргумента, вы должны записать нечто такое в верхней части вашей функции:

Пример E-1. Аргументы функции

pval *arg1, *arg2;
if (ARG_COUNT(ht) != 2 || getParameters(ht,2,&arg1,&arg2)==FAILURE) {
   WRONG_PARAM_COUNT;
}
Примечание: Аргументы могут передаваться по значению или по ссылке. В обоих случаях вы  должны передать &(pval *) в getParameters. Если вы хотите проверить, передан n-ный параметр по ссылке или нет, вы можете использовать функцию ParameterPassedByReference(ht,n). Она возвратит 1 или 0.

Если вы изменяете любой из переданных параметров, переданы ли они по ссылке или нет, вы можете начать работу с данным параметром, вызвав pval_destructor на нём, или, если это ARRAY, в который вы хотите добавить данные, вы можете использовать функции, аналогичные тем, которые находятся в internal_functions.h и манипулируют return_value как ARRAY.

Также, если вы изменяете параметр на IS_STRING, не забудьте сначала присвоить новую estrdup()'ированную строку и размер строки и только после этого изменяйте тип на IS_STRING. Если вы изменяете строку параметра, который уже является IS_STRING или IS_ARRAY, вы должны сначала запустить на нём pval_destructor.

Переменное количество аргументов функции

Функция может принимать переменное количество аргументов. Если ваша функция может принять 2 или 3 аргумента, используйте следующее:

Пример E-2. Переменное количество аргументов функции

pval *arg1, *arg2, *arg3;
int arg_count = ARG_COUNT(ht);

if (arg_count < 2 || arg_count > 3 ||
    getParameters(ht,arg_count,&arg1,&arg2,&arg3)==FAILURE) {
    WRONG_PARAM_COUNT;
}

Использование аргументов функции

Тип каждого аргумента хранится в поле (свойстве) типа pval. Этот тип может быть одним из следующих:

Таблица E-1. Внутренние типы PHP
IS_STRINGСтрока
IS_DOUBLEЧисло с плавающей точкой двойной точности
IS_LONGДлинное целое число
IS_ARRAYМассив
IS_EMPTY None/Ничего
IS_USER_FUNCTION ??
IS_INTERNAL_FUNCTION ?? (если что-то из этого не может быть передано функции - удаляется)
IS_CLASS??
IS_OBJECT ??

Если вы получаете аргумент одного типа и хотите использовать его как другой тип, или если вы просто хотите форсировать приведение аргумента к определённому типу, вы можете использовать одну из следующих функций конвертации типа:
convert_to_long(arg1);
convert_to_double(arg1);
convert_to_string(arg1); 
convert_to_boolean_long(arg1); /* Если строка равна "" или "0", она становится 0,
			        в ином случае 1 */
convert_string_to_number(arg1);  /* Конвертирует строку в LONG или в
				 DOUBLE, в зависимости от строки */

Все эти функции выполняют конвертацию на месте/in-place. Они ничего не возвращают.

Реальный аргумент хранится в union; членами являются:

  • IS_STRING: arg1->value.str.val

  • IS_LONG: arg1->value.lval

  • IS_DOUBLE: arg1->value.dval

Работа с памятью в функциях

Любая необходимая функции память должна быть выделена с помощью emalloc() или estrdup(). Это абстрактные функции работы с памятью, которые выглядят и работают как нормальные функции malloc() и strdup(). Память должна освобождаться с помощью efree().

В программе имеются два вида памяти: память, которая возвращается разборщику в переменной, и память, которая нужна вам для временного хранения в вашей внутренней функции. Если вы присваиваете строку переменной, возвращаемой разборщику, вы должны сначала выделить память с помощью emalloc() или estrdup(). Эта память НИКОГДА НЕ ДОЛЖНА освобождаться вами, если только вы позднее не переписываете в этой же функции первоначальное присвоение (хотя этот стиль программирования нельзя приветствовать).

Для любого временного/постоянного выделения памяти, необходимой вам в функции/библиотеке, вы должны использовать три функции: emalloc(), estrdup() и efree(). Они ведут себя ТОЧНО ТАК ЖЕ, как и их функции-двойники. Всё что вы emalloc() или estrdup(), вы должны efree() в той или иной точке, если это не предполагается делать в конце программы; иначе это приведёт к утечке памяти. Фраза "Они ведут себя ТОЧНО ТАК ЖЕ, как и их функции-двойники" означает: если вы efree() то, что не было emalloc()'овано или estrdup()'овано, вы можете получить нарушение сегментации. Поэтому, пожалуйста, будьте бдительны и освобождайте всю выделенную память.

Если вы компилируете с опцией "-DDEBUG", PHP будет выводить весь листинг памяти, выделенной с помощью emalloc() и estrdup(), но не освобождённой с помощью efree(), при запуске специфицированного скрипта.

Установка переменных в Таблице Символов

Имеются несколько макросов, которые облегчают установку переменной в таблице символов:

  • SET_VAR_STRING(name,value)

  • SET_VAR_DOUBLE(name,value)

  • SET_VAR_LONG(name,value)

Предупреждение

Соблюдайте осторожность при использовании SET_VAR_STRING. Для части value обязана быть выделена память, поскольку код обслуживания памяти будет пытаться освободить эту позицию позднее. Не передавайте статически размещённую память в SET_VAR_STRING.

Таблицы символов в PHP реализованы как хэш-таблицы. В любое данное время &symbol_table является указателем на 'главную/main' таблицу символов, а active_symbol_table указывает на текущую активную таблицу символов (они могут быть идентичны, как при старте, или разными, если вы находитесь внутри функции).

В следующих примерах использована 'active_symbol_table'. Вы должны заменить её на &symbol_table, если хотите работать именно с 'main' таблицей символов. Также эта же функция может применяться к массивам, как разъясняется ниже.

Пример E-3. Проверка существования $foo а таблице символов

if (hash_exists(active_symbol_table,"foo",sizeof("foo"))) { exists... }
else { doesn't exist }

Пример E-4. Определение размера переменной в таблице символов

hash_find(active_symbol_table,"foo",sizeof("foo"),&pvalue);
check(pvalue.type);

Массивы в PHP реализованы с использованием тех же хэш-таблиц, что и в таблицах символов. Это значит, что две вышеприведённые функции могут также использоваться для проверки переменных в массивах.

Если вы хотите определить новый массив в таблице символов, вы должны сделать следующее:

Сначала может понадобиться проверка существования массива и соответствующего выхода через использование hash_exists() или hash_find().

Инициализируем массив:

Пример E-5. Инициализация нового массива

pval arr;

if (array_init(&arr) == FAILURE) { failed... };
hash_update(active_symbol_table,"foo",sizeof("foo"),&arr,sizeof(pval),NULL);

Этот код объявляет новый массив $foo в активной таблице символов. Это пустой массив.

Вот как добавить в него новые вхождения:

Пример E-6. Добавление вхождений в новый массив

pval entry;
  
entry.type = IS_LONG;
entry.value.lval = 5;
  
/* определяется $foo["bar"] = 5 */
hash_update(arr.value.ht,"bar",sizeof("bar"),&entry,sizeof(pval),NULL); 

/* определяется $foo[7] = 5 */
hash_index_update(arr.value.ht,7,&entry,sizeof(pval),NULL); 

/* определяется следующее свободное место в $foo[],
 * $foo[8], как 5 (работает аналогично php2)
 */
hash_next_index_insert(arr.value.ht,&entry,sizeof(pval),NULL);

Если вы хотите изменить значение, вставленное в хэш, вы обязаны сначала запросить его из хэша. Чтобы избежать этой лишней работы, вы можете предоставить pval ** функции add хэша, и он будет обновлён pval *-адресом элемента, вставленного в хэш. Если это значение равно NULL (как во всех предыдущих примерах) - этот параметр игнорируется.

hash_next_index_insert() использует примерно ту же логику, что и "$foo[] = bar;" в PHP 2.0.

Если вы конструируете массив для возвращения из функции, вы можете инициализировать этот массив, выполнив следующее:

if (array_init(return_value) == FAILURE) { failed...; }

... а затем добавляя значения вспомогательными функциями:

add_next_index_long(return_value,long_value);
add_next_index_double(return_value,double_value);
add_next_index_string(return_value,estrdup(string_value));

Конечно, если добавление не выполняется сразу после инициализации массива, вам сначала нужно будет найти этот массив:
pval *arr;
  
if (hash_find(active_symbol_table,"foo",sizeof("foo"),(void **)&arr)==FAILURE)
{ can't find... }
else { use arr->value.ht... }

Заметьте, что hash_find получает указатель на указатель pval, а не указатель pval.

Любая функция хэша возвращает SUCCESS или FAILURE (кроме hash_exists(), которая возвращает булево значение).

Возвращение простых значений

Имеются несколько макросов для облегчения получения return-значений из функций.

Все макросы RETURN_* устанавливают return-значение и возвращают его из функции:

  • RETURN

  • RETURN_FALSE

  • RETURN_TRUE

  • RETURN_LONG(l)

  • RETURN_STRING(s,dup)  Если dup равно TRUE, дублирует строку

  • RETURN_STRINGL(s,l,dup)  Возвращает строку (s), специфицируя длину (l)

  • RETURN_DOUBLE(d)

Макросы RETVAL_* устанавливают, но не возвращают, return-значение.

  • RETVAL_FALSE

  • RETVAL_TRUE

  • RETVAL_LONG(l)

  • RETVAL_STRING(s,dup)  Если dup равно TRUE, дублирует строку

  • RETVAL_STRINGL(s,l,dup)   Возвращает строку (s), специфицируя длину (l)

  • RETVAL_DOUBLE(d)

Все вышеприведённые строковые макросы будут estrdup() передаваемый аргумент 's', поэтому вы можете безопасно освободить аргумент после вызова макроса или, альтернативно, использовать статически размещённую память.

Если ваша функция возвращает булевы ответы success/error, всегда используйте RETURN_TRUE и RETURN_FALSE, соответственно.

Возвращение сложных значений

Ваша функция может также возвращать сложные типы данных, такие как объект/object или массив/array.

Возвращение объекта:

  1. Вызвать object_init(return_value).

  2. Заполнить его значениями. Функции, предназначенные для этого, перечислены ниже.

  3. Возможно, зарегистрировать функции для этого объекта. Чтобы получать значения из объекта, функции понадобится получить "this" из active_symbol_table. Его тип должен быть IS_OBJECT, и это как правило таблица регулярного хэш (т.е. вы можете использовать функции регулярного хэша на .value.ht). Фактическая регистрация функции может быть выполнена с использованием :
    add_method( return_value, function_name, function_ptr );

Функции для заполнения объекта:

  • add_property_long( return_value, property_name, l ) - Добавляет свойство с именем 'property_name', типом long, равное 'l'

  • add_property_double( return_value,property_name, d ) - То же самое, только double

  • add_property_string( return_value,property_name, str ) - То же самое, только string

  • add_property_stringl( return_value,property_name, str, l ) - То же самое, только string длиной 'l'

Возвращение массива:

  1. Вызвать array_init(return_value)

  2. Заполнить его значениями. Функции для этого перечислены ниже.

Вот функции для заполнения массива:

  • add_assoc_long(return_value,key,l) - добавляет ассоциативное вхождение/entry с ключом 'key' и long-значением 'l'

  • add_assoc_double(return_value,key,d)

  • add_assoc_string(return_value,key,str,duplicate)

  • add_assoc_stringl(return_value,key,str,length,duplicate) - специфицирует длину строки

  • add_index_long(return_value,index,l) - добавляет вхождение в индексе 'index' с long-значением 'l'

  • add_index_double(return_value,index,d)

  • add_index_string(return_value,index,str)

  • add_index_stringl(return_value,index,str,length)- специфицирует длину строки

  • add_next_index_long(return_value,l) - добавляет вхождение в массиве в следующем свободном смещении с long-значением 'l'

  • add_next_index_double(return_value,d)

  • add_next_index_string(return_value,str)

  • add_next_index_stringl(return_value,str,length) - специфицирует длину строки

Использование списка ресурсов

PHP имеет стандартные способы работы с ресурсами различных типов. Это замена всех локальных связанных списков PHP 2.0.

Доступны функции:

  • php3_list_insert(ptr, type) - возвращает 'id' вновь вставленного ресурса

  • php3_list_delete(id) - удаляет ресурс со специфицированным id

  • php3_list_find(id,*type) - возвращает указатель ресурса со специфицированным id, обновляет 'type' до типа ресурса

Обычно эти функции используются для SQL-драйверов, но могут также использоваться для чего угодно; например, для обслуживания дескрипторов файлов.

Типичный листинг кода выглядит так:

Пример E-7. Добавление нового ресурса

RESOURCE *resource;

/* ...разместить память для ресурса и получить ресурс... */
/* добавить новый ресурс в список */
return_value->value.lval = php3_list_insert((void *) resource, LE_RESOURCE_TYPE);
return_value->type = IS_LONG;

Пример E-8. Использование существующего ресурса

pval *resource_id;
RESOURCE *resource;
int type;

convert_to_long(resource_id);
resource = php3_list_find(resource_id->value.lval, &type);
if (type != LE_RESOURCE_TYPE) {
    php3_error(E_WARNING,"resource index %d has the wrong type",resource_id->value.lval);
    RETURN_FALSE;
}
/* ...использовать ресурс... */

Пример E-9. Удаление существующего ресурса

pval *resource_id;
RESOURCE *resource;
int type;

convert_to_long(resource_id);
php3_list_delete(resource_id->value.lval);

Типы ресурсов должны быть зарегистрированы в php3_list.h, в enum list_entry_type. Кроме того, нужно добавить shutdown-код для любого вновь определённого типа ресурса в list.c's list_entry_destructor() (даже если вы ничего особенного при отключении/shutdown делать не хотите, вы обязаны добавить пустой case).

Использование постоянной таблицы ресурсов

В PHP есть стандартный способ хранения постоянных ресурсов (т.е. ресурсов, хранимых между вызовами). Первым эту возможность использовал модуль MySQL, а затем mSQL, поэтому можно получить представление о том, как нужно использовать постоянные ресурсы, просмотрев mysql.c. Вам нужно просмотреть функции:
php3_mysql_do_connect
php3_mysql_connect()
php3_mysql_pconnect()

Общая идея постоянных модулей такова:

  1. Кодировать все ваши модули для работы со списком регулярных ресурсов, рассмотренным в разделе (9).

  2. Кодировать дополнительные connect-функции, которые проверяют, существует ли уже ресурс в списке постоянных ресурсов. Если существует, зарегистрировать его в списке постоянных ресурсов как указатель на список существующих ресурсов (поскольку если 1., остальной код должен сработать немедленно). Если не существует, создать его, добавить его в список существующих ресурсов И добавить указатель на него из списка регулярных ресурсов, чтобы весь код мог работать, пока он находится в списке регулярных ресурсов, но при следующем подключении ресурс может быть найден уже в списке существующих ресурсов и использован без  необходимости его повторного создания. Вы должны регистрировать эти ресурсы с различными типами (например, LE_MYSQL_LINK для несуществующей ссылки и LE_MYSQL_PLINK - для существующей ссылки).

Если вы просмотрите mysql.c, обратите внимание, что, за исключением более сложной функции connect, остальная часть модуля не изменилась.

Точно такой же интерфейс имеется для списка регулярных ресурсов и для списка постоянных ресурсов, только 'list' заменено на 'plist':

  • php3_plist_insert(ptr, type) - возвращает 'id' вновь вставленного ресурса

  • php3_plist_delete(id) - удаляет ресурс со специфицированным id

  • php3_plist_find(id,*type) - возвращает указатель ресурса со специфицированным id, обновляет 'type' на тип этого ресурса

Более вероятно, однако, что эти функции окажутся бесполезны при попытке реализовать постоянный модуль. Обычно бывает нужно использовать тот факт, что список постоянных ресурсов является в действительности хэш-таблицей. Например, в модулях MySQL/mSQL, когда есть вызов pconnect() (постоянное connect), функция строит строку вывода host/user/passwd, которая передаётся этой функции, и хэширует SQL-ссылку этой строкой как ключом/key. При следующем вызове pconnect() с теми же host/user/passwd, будет сгенерирован тот же key, и функция найдёт SQL-ссылку в постоянном списке.

Пока документация отсутствует, вы должны просмотреть в mysql.c или в msql.c, как можно использовать возможности хэш-таблиц plist'ов.

Одно важное замечание: ресурсы, входящие в список постоянных ресурсов обязаны *НЕ* размещаться менеджером памяти PHP, т.е. они НЕ должны создаваться с помощью emalloc(), estrdup(), etc.
Вместо этого нужно использовать регулярные malloc(), strdup(), etc.
Причина проста - в конце запроса (в конце запуска) каждый участок памяти, выделенный менеджером памяти PHP, удаляется. Поскольку не предполагается удаление постоянного списка в конце запроса, менеждер памяти PHP нельзя использовать для размещения ресурсов, входящих в этот список.

Если вы регистрируете ресурс, вводимый в постоянный список, вы должны добавить для него деструктор как в постоянный, так и в непостоянный списки. Деструктор в деструкторе непостоянного списка ничего делать не должен. Деструктор в деструкторе постоянного списка должен соответствующим образом освобождать ресурсы, полученные данным типом (память, SQL-ссылки etc). Как и с непостоянными ресурсами, вы *ОБЯЗАНЫ* добавить деструкторы каждому ресурсу, даже если он не требует уничтожения и деструктор может быть пустым. Запомните: поскольку emalloc() и компания не используются в сочетании с постоянным списком, вы обязаны не использовать здесь efree() вообще.

Добавление директив конфигурации времени прогона/runtime

Многие свойства PHP могут конфигурироваться на этапе прогона программы. Эти директивы конфигурации могут появляться или в файле php3.ini, или - в случае с версией Apache-модуля - Apache .conf-файлах. Преимущество их присутствия в Apache .conf-файлах заключается в том, что они могут быть сконфигурированы на уровне директорий. Это означает, что одна директория может иметь одну safemodeexecdir, например, а другая директория - другую. Эта дробность конфигурации особенно необходима, когда сервер поддерживает наличие несколько виртуальных хостов.

Вот шаги по добавлению новой директивы:

  1. Добавить директиву в структуру php3_ini_structure в mod_php3.h.

  2. В main.c отредактировать функцию php3_module_startup и добавить соответствующий вызов cfg_get_string() или cfg_get_long().

  3. Добавить директиву, ограничения и комментарий в структуру php3_commands в mod_php3.c. Обратите внимание на часть restrictions/ограничений. RSRC_CONF являются директивами, которые могут находится только в текущих Apache .conf-файлах. Любые директивы OR_OPTIONS могут находиться в любом месте, включая нормальные .htaccess-файлы.

  4. В php3take1handler() или в php3flaghandler() добавить соответствующее вхождение для вашей директивы.

  5. В разделе конфигурации/configuration функции _php3_info() в functions/info.c вам необходимо добавить вашу новую директиву.

  6. И наконец - вы, конечно, должны использовать новую вашу директиву где-нибудь. Она будет адресоваться как php3_ini.directive.


Назад Оглавление Вперёд
Протокол ОтладчикаВверх Вызов пользовательских функций