PHP 3.0 полностью изменен. Его лексический анализатор стал намного более логичным и последовательным чем 2.0; версия 3.0 быстрее, и использует меньше ресурсов. Однако, некоторые усовершенствования повлекли частичную несовместимость в синтаксисе и функциональных возможностях.
Кроме того, в PHP 3.0 улучшен синтаксис и семантика, что также повлекло некоторую несовместимость. Однако, мы надеемся, что все эти усовершенствования к лучшему.
Эта глава поможет вам решить проблемы связанные с несовместимостью при переходе от PHP/FI 2.0 к PHP 3.0. Новые возможности здесь не рассматриваются.
Существует программа, которая может автоматически конвертировать старый PHP/FI 2.0 скрипт; вы можете найти ее в подкаталоге convertor дистрибутива PHP 3.0. Эта программа только отслеживает изменения синтаксиса, поэтому, в любом случае, вам придется прочитать эту главу внимательно.
Первое, что вы вероятно заметите - это то что открывающий и закрывающий тэги PHP изменены. Старая <?> форма была заменена тремя новыми возможными формами:
Пример 0-1. Изменение: старые открывающий и закрывающий тэги:
<? echo "This is PHP/FI 2.0 code.\n"; > |
Начиная с версии 2.0, PHP/FI поддерживает также следующий формат:
Пример 0-2. Изменение: новые открывающий и закрывающий тэги, первый вариант:
<? echo "This is PHP 3.0 code!\n"; ?> |
Заметьте, что закрывающий тэг теперь состоит из знака вопроса и знака "больше" вместо только "больше". Однако, если Вы планируете использовать XML на вашем сервере, у вас будут проблемы с этим вариантом, так как PHP может попробовать исполнить разметку XML в документах XML как код PHP. Из-за этого, было внесено следующее изменение:
Пример 0-3. Изменение: новые тэги начала и конца, второй вариант:
<?php echo "This is PHP 3.0 code!\n"; ?> |
Из-за проблем с редакторами, не поддерживающими инструкции обработки (например Microsoft FrontPage), были введены следующие изменения:
Пример 0-4. Изменение: новые тэги начала и конца, третий вариант:
<script language="php"> echo "This is PHP 3.0 code!\n"; </script> |
"Альтернативный" способ описания блока if/elseif/else, с использованием, if(); elseif (); else; endif; не мог быть эффективно осуществлен без серьезного усложнения компиллятора/интерпретатора, из-за этого его синтаксис был изменен:
Пример 0-5. Изменение: старый синтаксис if..endif:
if ($foo); echo "yep\n"; elseif ($bar); echo "almost\n"; else; echo "nope\n"; endif; |
Пример 0-6. Изменение: новый синтаксис if..endif:if ($foo): echo "yep\n"; elseif ($bar): echo "almost\n"; else: echo "nope\n"; endif; |
Точки с запятой были заменены двоеточиями во всех операторах, за исключением завершающего блок (endif).
Точно так же как, с if..endif, был изменен синтаксис while..endwhile:
Пример 0-7. Изменения: старый, while..endwhile синтаксис:
while ($more_to_come); ... endwhile; |
Пример 0-8. Изменения: новый синтаксис while..endwhile:
while ($more_to_come): ... endwhile; |
Внимание! |
Используя устаревший синтаксис в PHP 3.0 вы получите бесконечный цикл. |
В PHP/FI 2.0 использовалась левая часть выражения для определения типа результата. PHP 3.0 учитывает обе части выражения для определения типа результата; это может привести к неожиданным результатам работы скриптов 2.0.
Рассмотрите этот пример:
$a[0]=5; $a[1]=7; $key = key($a); while ("" != $key) { echo "$keyn"; next($a); }
В PHP/FI 2.0 мы получили бы индексы $a. В PHP 3.0 мы не увидим ничего. Причина в том что в PHP 2.0, переменная в левой части выражения - строка; было выполнено сравнение, действительно "" не равно "0", и цикл был пройден. В PHP 3.0, при выполнении операции сравнения строковой и целочисленных переменных, строка будет преобразована в целое число и далее аргументы сравниваются как целые. Это означает в данном случае, что сравненивается значение функции atoi("") которое равно 0, и variablelist которое также равно 0; цикл не выполняется ни разу.
Исправить это достаточно просто. Замените начало на:
while ((string)$key != "") {
Сообщения об ошибках PHP 3.0, как правило, точнее чем в 2.0. Вместо указания фрагмента кода, вызвавший ошибку, вы получаете имя файла и номер строки.
В PHP 3.0 используется метод сокращенного вычисления логических выражений. Это означает что в выражении (1 || test_me()), функция test_me() не вызывается, так как результат функции уже не сможет изменить результат этого логического выражения.
Эта незначительная, на первый взгляд, проблема совместимости может приести к неожиданным последствиям.
Значения true/false, возвращаемые функциямиБольшинство внутренних функции были переписаны; теперь они возвращают TRUE, в случае удачи и FALSE в противном случае, тогда как в PHP/FI 2.0 возвращаются 0 и -1 соответственно. Эти новые возможности позволяют создавать более логичный код, такой так $fp = fopen("/your/file") or fail("darn!");. Так как PHP/FI 2.0 не имел четких правил, относительно того, что должна вернуть функция в случае неудачи, в большинстве случаев скрипты использующие подобные функции должны быть проверены вручную после проверки конвертером.
Пример 0-9. Изменения 2.0: возвращаемые значения, старый код:
$fp = fopen($file, "r"); if ($fp == -1); echo("Could not open $file for reading<br>\n"); endif; |
Пример 0-10. Изменения 2.0: возвращаемые значения, новый код:
$fp = @fopen($file, "r") or print("Could not open $file for reading<br>\n"); |
То есть вы не можете читать массив в цикле, выполняющем $data = $array[]. Используйте current()и next().
Кроме того, выражение $array1[] = $array2 не добавляет значения массива $array2 к $array1, но добавляет $array2 как последний элемент $array1. См. также поддерку многомерных массивов.
Пример 0-11. Изменения 2.0: сложение для строковых переменных
echo "1" + "1"; В PHP 2.0 значение этого выражения - 11, а в PHP 3.0 - 2. Используйте вместо него: echo "1"."1"; $a = 1; $b = 1; echo $a + $b; Значение этого выражения = 2 для PHP 2.0 и 3.0. $a = 1; $b = 1; echo $a.$b; Это выражение вернет 11 в PHP 3.0. |
Все функции выглядят следующим образом:
void php3_foo(INTERNAL_FUNCTION_PARAMETERS) { }
Это общий вид функции, даже если она не имеет аргументов.
Аргументы всегда имеют тип pval. Этот тип представляет собой объединение (union), в котором содержится фактический тип аргумента. В том случае, если ваша функция принимает несколько аргументов, вы можете сделать что-нибудь вроде этого в начале вашей функции:
Пример 0-1. Объявление переменных:
pval *arg1, *arg2; if (ARG_COUNT(ht) != 2 || getParameters(ht,2,&arg1,&arg2)==FAILURE) { WRONG_PARAM_COUNT; } |
Обратите внимание: аргументы могут быть по значению или по ссылке. В любом случае вы должны передать &(pval *) в getParameters. Если вы хотите проверить, был ли параметр передан ссылкой или нет, вы можете использовать функцию ParameterPassedByReference(ht,n), она вернет 1 или 0.
Если изменяете один из параметров, переданный по ссылке или по значению, вы можете вызвать pval_destructor, или, в том случае, если это массив, вы можете использолвать функцию, подобную одной из internal_functions.h, в котоых return_value является массивом.
Если вы собираетесь преобразовывать параметр к IS_STRING - сначала создайте новую строку с помощью estrdup() и укажите ее длину, только после этого преобразуйте к IS_STRING. Если вы изменяете строку параметра уже являющегося IS_STRING или IS_ARRAY, сначала придется использовать pval_destructor.
Функция может принимать неопределенное количество аргументов. Например, в случае, если ваша функция принимает два или три аргумента, можно использовать следующий код:
Пример 0-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; } |
Тип каждого аргумента означен в поле type структуры pval. Этот тип может быть любым из приведенных ниже:
Таблица 0-1. Типы переменных PHP
IS_STRING | String |
IS_DOUBLE | Double-precision floating point |
IS_LONG | Long integer |
IS_ARRAY | Array |
IS_EMPTY | None |
IS_USER_FUNCTION | ?? |
IS_INTERNAL_FUNCTION | ?? (если не может быть передана в функцию - удаляется) |
IS_CLASS | ?? |
IS_OBJECT | ?? |
Если вы получаете аргумент одного типа и хотели бы использовать его как аргумент другого типа, или если вы хотите жестко определить тип аргумента - используйте одну из преобразующих функций:
convert_to_long(arg1);Эти функции только выполняют преобразование аргумента, они не возвращают значений.
Тип переменной обозначается в объединении:
Память, необходимая внутри функции, должна быть зарезервирована функциями emalloc() или estrdup(). Это функции управления памятью, которые выглядят и работают как обычные malloc() и strdup(). Память освобождается с помощью efree().
В программе могут быть использованы два вида памяти: область памяти, которая будет использована переменными, и временная память для функций. Когда вы присваиваете строковое значение переменной первого типа, вы должны предварительно выделить область памяти при помощи emalloc() or estrdup(). Вы НЕ должны освобождать эту память, если только вы не переписываете знаение переменной в той же функции (это считается не очень хорошим стилем программирования).
Для работы с временной/постоянной памятью необходимо использовать три функции: emalloc(), estrdup(), и efree(). Они работают ТАКЖЕ как их "двойники". Память, выделенную с помощью emalloc() или estrdup() вы должны освободить с помощью efree(), иначе область памяти будет утеряна. Под словами "также, как двойники" мы имеем в виду следующее: если вы пытаетесь освободить память, которая не была выделена с помощью emalloc() или estrdup() вы скорее всего получите ошибку сегментации (segmentation fault). Поэтому будьте внимательны и не забывайте освобождать память после использования.
Если вы выполняете компиляцию с опцией "-DDEBUG", PHP3 выдаст список всех блоков памяти, которые были выделены м помощью emalloc() или estrdup(), но не будут освобождены после завершения скрипта.
Для упрощения доступа к переменным в таблице символов определены следующие макросы:
SET_VAR_STRING(имя,значение) [1]
SET_VAR_DOUBLE(имя,значение)
SET_VAR_LONG(имя,значение)
Таблицы символов в PHP 3.0 реализованы в виде хэш-таблиц (hash table). В любой момент времени, &symbol_table указывает на 'главную' таблицу символов, а active_symbol_table указывает на активную в данный момент (они идентичны при запуске, или различны - внутри функции).
Следующие примеры используют 'active_symbol_table'. Вам придется заменить это на &symbol_table ели вы хотите работать с 'главной' таблицей символов. Те же самые функции можно применять и к массивам,как показано ниже.
Пример 0-3. Проверка наличия $foo в таблице символов
if (hash_exists(active_symbol_table,"foo",sizeof("foo"))) { существует... } else { не существует } |
Пример 0-4. Вычисление размера переменной в таблице символов
hash_find(active_symbol_table,"foo",sizeof("foo"),&pvalue); check(pvalue.type); |
В PHP 3.0 маcсивы реализованы с использованием тех же хэш-таблиц, что и в таблицах символов. Это означает что приведенные функции можно использовать и для проверки переменных внутри массивов.
В том случае, если вы хотите определить новое множество в таблице символов:
Сначала, вы можете проверить наличие, используя hash_exists() или hash_find().
Потом инициализируйте множество:
Пример 0-5. инициализация нового множества
pval arr; if (array_init(&arr) == FAILURE) { failed... }; hash_update(active_symbol_table,"foo",sizeof("foo"),&arr,sizeof(pval),NULL); |
Объявление нового (пустого) массива $foo в активной таблице символов.
Вот так вы можете добавить новые элементы:
Пример 0-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], to be 5 (работает как и в php2) */ hash_next_index_insert(arr.value.ht,&entry,sizeof(pval),NULL); |
Если вы хотите изменить значение, в хэш-таблице, вы должны сначала считать его. Для того чтобы избежать этого на верхнем уровне, вы можете добавлять pval ** к функции выполняющей добавление (обновление произойдет с адресом 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;Обратите внимание: hash_find получает лишь ссылку на указатель на pval, а не сам указатель на pval.
Практически любая hash-функция возвращает SUCCESS или FAILURE (hash_exists() возвращает булево значение).
Для упрощения возврата значений из функций определены следующие макросы:
Макросы RETURN_* устанавливают значение возвращаемое значение функции и возвращают управление:
Макросы RETVAL_* устанавливают возвращаемое значение, но не возвращает управление.
Все строковые макросы вызывают функцию estrdup() для переданного аргумента, поэтому вы можете спокойно освободить память, занятую под аргумент после выполнения макроса, или использовать статическую память.
Если ваша функция возвращает логическое значение, используйте RETURN_TRUE и RETURN_FALSE.
Ваша функция может также возвращать и сложные значения; такие как объект или массив.
Возвращение объекта:
Регистрация метода выполняется следующим образом:
add_method( return_value, function_name, function_ptr );
Функции для добавления полей(свойств):
Возвращение массива:
Функции для добавления элементов:
В PHP 3.0 имеется стандартные методы для работы с разными типами ресурсов. Это сделано для замены локально связанных списков в PHP 2.0.
Доступные функции:
Обычно эти функции используются драйверами SQL, хотя и могут используваться в любом другом месте, например, при работе с файловыми дескрипторами.
Пример работы с этими функциями показан ниже.
Пример 0-7. Добавление нового ресурса
RESOURCE *resource; /* ...выделение памяти под ресурс и создание его... */ /* добавление ресурса в список */ return_value->value.lval = php3_list_insert((void *) resource, LE_RESOURCE_TYPE); return_value->type = IS_LONG; |
Пример 0-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,"ресурс с номером %d: неправильный тип",resource_id->value.lval); RETURN_FALSE; } /* ...использование ресурса... */ |
Пример 0-9. Удаление ресурса
pval *resource_id; RESOURCE *resource; int type; convert_to_long(resource_id); php3_list_delete(resource_id->value.lval); |
Типы ресурсов должны быть зарегистрированы в php3_list.h в списке list_entry_type. Кроме того, вы должны написать деструктор для каждого типа ресурсов в list_entry_destructor() файла list.c. Если вы не предпринимаете никаких дополнительных действий в деструкторе, вы должны добавить пустой деструктор.
В PHP 3.0 существует методы хранения постоянных ресурсов (ресурсов, которые сохраняются между вызовами). Один из модулей, который использует эту возможность - модуль MySQL, а также mSQL; вы можете изучить правила использования постоянных ресурсов читая код mysql.c. Следует обратить внимание на функции:
php3_mysql_do_connect |
php3_mysql_connect() |
php3_mysql_pconnect() |
Основная идея таблиц постоянных ресурсов заключается в следующем:
Если вы откроете файл mysql.c, вы заметите, что переписывать ничего не пришлось, за исключением более сложных функций связи.
Существует набор функций для работы с таблицами постоянных ресурсов, в котором функции для работы с таблицами постоянных ресурсов аналогичны функциям для работы с обычными списками ресурсов. В этом случае 'list' заменяется на 'plist':
Однако, эти функции могут оказаться бесполезными когда вы создаете постоянный модуль. Можно пользоваться тем, что постоянные таблицы ресурсов реализованы как хэш-таблицы. Например, в модулях MySQL/mSQL, когда вызывается pconnect(), создается строка на основе имени хоста, имени пользователя и его пароля и эта строка помещается в хэш-таблицу как ключ. В следующий раз когда будет вызвана pconnect() с теми же параметрами хоста, пользователя и пароля, будет сгенерирован тот же самый ключ и SQL-связь будет найдено в списке постоянных ресурсов.
Чтобы более полно понять работу функций plist* с хэш-таблицами ознакомтесь с файлами mysql.c или msql.c.
Внимание: ресурсы, предназначеные для таблицы постоянных ресурсов НЕ ДОЛЖНЫ создаваться с помощью менеджера памяти, т.е. они не должны быть созданы с использованием emalloc(), estrdup() и т.д. Вместо этого вы должные использовать обычные malloc(), strdup() и т.д. Причина этого проста: после исполнения запроса каждый участок памяти, выделенный меджером, удаляется. Следовательно не можете использовать менеджер памяти, так как список постоянных ресурсов не должен быть удален в конце сеанса.
Когда вы регистрите ресурс, который собираетесь добавить в таблицу постоянных, вы должны добавить деструкторы для них и в таблицу постоянных ресурсов, и обычных. Дескриптор в таблице обычных ресурсов должен быть пустым. Напротив, деструктор постоянного ресурса должен правильно освободить память, SQL-связи и т.д. Помните, что вы обязаны предусмотреть деструктор для любого типа ресурсов, даже если он будет пустым. Помните так же о том, что из-за того, что функции типа emalloc() не используются с постоянными ресурсами, вы не должны пользоваться в деструкторе функциями типа efree().
Многие возможности PHP3 могут быть сконфигурированы во время выполнения. Конфигурационные директивы могут быть либо в файле php3.ini, либо, в случае использования Apache, в файлах .conf модуля Apache. Преимущесво использования файлов .conf модуля Apache заключается в том, что в этом случае можно предусмотреть настройки для каждого каталога в отдельности. Эта возможность особенна полезна когда сервер поддерживает несколько виртуальных хостов.
Ниже описаны действия, которые необходимо предпринять для добавления директивы:
Для вызова пользовательских функций из внутренней нужно использовать call_user_function().
Функция call_user_function() возвращает SUCCESS в случае успеха, и FAILURE в том случае если функция не найдена. Вы должны проверить возвращенное значение! Если возврашен SUCCESS, вы отвечаете за удаление retval и pval. В случае возврата FAILURE, значение retval не определено.
Все внутренние функции должны быть сконструированы так, чтобы возможно было повторное использование ее. Это в частности означает, что в них не должны использоваться глобальные или статические переменные.
Функция call_user_function() принимает шесть аргументов:
Хэш-таблица в которой производится поиск функции.
Указатель на объект, с которым вызывается функция. NULL если вызывается глобальная функция. В первом случае аргумент function_table игнорируется и берется и определяется из объекта. Объект может быть изменен функцией (внутри функции он доступен через указатель $this). Если вы не хотите, чтобы это произошло, передайте копию объекта.
Имя вызываемой функции. Должно быть pval IS_STRING со значениями function_name.str.val и function_name.str.len установленными соответственно. Значение function_name изменяется вызовом call_user_function() - конвертируется в нижний регистр (прописные буквы). Если вы этого не хотите, передайте копию имени функции.
Указатель на структуру pval, в которую записывается возвращаемое функцией значение. Структура должна быть создана заранее, call_user_function() сама по себе не создает ничего.
Число параметров, передаваемое функции.
Массив указателей на значения, которые передаются в вызываемую функцию; первый аргумент имеет смещение 0, второй 1 и т.д. Массив является массивом указателей на pval. Аргументы пересылаются по ссылке, из чего следует, что если функция меняет аргумент, меняется и первоначальное значение. Опять же, если вы хотите этого избежать, передайте ссылку на копию объекта.
Чтобы выдать сообщение об ошибке внутри функций вы должны использовать функкцию php3_error(). Она принимает как минимум два параметра -- первый это категория ошибки, второй - форматирующая строка (как в стандартной функции printf()), за которым могут следовать аргументы форматирующей строки. Ниже представлены категории ошибок:
Сообщения этого типа по умолчанию не выводятся и сигнализируют о том, что в скрипте произошло что-то, что может означать ошибку, но может случится и при нормальном исполнении скрипта. Примерами могут служить попытка обращения к переменной, значение которой не определено, или вызов stat() для несуществующего файла.
Сообщения этого типа выводятся, но не прерывают выполнение скрипта. Они сообщают, что существует ошибка, которая должна быть отслежена скриптом до того, как будет сделан вызов. Примером служит вызов ereg() с неправильным регулярным выражением.
Сообщения этого типа также выводятся по умолчанию, и выполнение скрипта прекращается. Они означают неустранимые ошибки, такие как невозможность выделения памяти.
Ошибки данного класса генерируются исключительно синтаксическим анализатором. Этот класс ошибок приведен здесь исключительно для полноты изложения.
То же, что и E_ERROR, за исключением того, что генерируются ядром PHP. В функциях не должны генерироваться сообщения данного типа.
То же, что и E_WARNING, за исключением того, что генерируются ядром PHP. В функциях не должны генерироваться сообщения данного типа.
Взгляд вглубь PHPОтладчик PHP полезен при отслеживании неочевидных ошибок. Он работает через TCP порт каждый раз, когда стартует PHP. Все сообщения об ошибках из запроса будут посланы по этому TCP соединению. Информация этого раздела нужна при работе с такими редакторами как Emacs, или в интегрированной среде разработки (IDE).
Действия по установке отладчика:
Теперь все предупреждения, замечания и т.д. будут показаны на этом сокете, даже если вы выключили error_reporting().
Протокол отладчика основан на отдельных строках. Каждая строка имеет свой тип; несколько строк могут составлять сообщение. Каждое сообщение начинается со строки с типом start и заканчивается строкой с типом end. PHP может посылать строки с несколькими сообщениями одновременно.
Строка имеет следующий формат:
date time host(pid) type: message-data
Таблица 0-1. Типы строк отладчика
Тип | Значенне |
---|---|
start | Сообщает получающей программе, что в этом месте начинается сообщение. Содержимое строки data содержит тип сообщения об ошибке (см. ниже). |
message | Сообщение об ошибке. |
location | Имя файла и номер строки, содержащей ошибку. Первая строка location содержит местоположение, data содержит информацию file:line. Строка location всегда следует после строки message и function. |
frames | Число кадров в дампе стека. Если их четыре, ожидается информация о четырех уровнях вызываемых функций. Если эта строка отсутствует, глубина предполагается нулевой (ошибка произошла на высшем уровне вложенности). |
function | Имя функции, содержащей ошибку. Повторяется один раз для каждого уровня в стеке вызовов функции. |
end | Сообщает получающей программе, что сообщение отладчика закончено. |
Таблица 0-2. Типы ошибок отладчика
Отладчик | Внутренняя в PHP |
---|---|
warning | E_WARNING |
error | E_ERROR |
parse | E_PARSE |
notice | E_NOTICE |
core-error | E_CORE_ERROR |
core-warning | E_CORE_WARNING |
unknown | (любая другая) |
Пример 0-1. Пример сообщения об ошибке
1998-04-05 23:27:400966 lucifer.guardian.no(20481) start: notice |