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

Самоучитель PHP 4 - Котеров Д. В

..pdf
Скачиваний:
93
Добавлен:
24.05.2014
Размер:
4.38 Mб
Скачать

288

Часть IV. Стандартные функции PHP

Да, это может показаться весьма искусственным приемом, но зато работает "на ура". Теперь вы можете быть уверены, что ваше письмо прочитает любой пользователь (особенно если оно послано в кодировке KOI8), даже если его почтовая программа вообще не настроена ни на какую кодировку. Можете похвастать этим достижением перед начальством, предложив ему поставить у себя в Outlook Express по умолчанию японскую кодировку, а затем попросив принять письмо, сгенерированное роботом по указанной схеме.

Перспективы: создание "умной" функции для отправки писем

Возможно, вам уже пришла в голову идея сделать универсальную функцию для рассылки писем — чтобы она сама добавляла к полю To в письме E-mail в угловых скобках (как в примере выше), проставляла нужную кодировку у письма (которая задается в параметрах при вызове функции), ну и т. д. Это вполне осуществимо. Функция может выглядеть, например, так:

bool PostMail(string $ToAddress, string $Encode, string $Message)

Посылает письмо $Message по адресу $ToAddress, перекодировав его предварительно в кодировку, заданную в $Encode. Параметр $Encode может принимать следующие значения:

rw — Windows

rk — KOI8-R

rm — Mac

ri — Iso Latin

rt — Translit

В письме автоматически проставляется Content-type...charset (если заголовок Content-type уже присутствует в письме, то он не портится, а просто у него меняется поле charset на нужное значение). Также корректируется поле To в письме. Одновременно правильно обрабатываются вставки PHP-кода в тело письма (можно использовать глобальные переменные и оператор echo). Для этого, как обычно, применяются "скобки" <? и ?>.

Реализацию поставленной задачи мы отложим до части V, где описаны и другие приемы, облегчающие работу на PHP. Для этого нам понадобится техника регулярных выражений, которыми мы вскоре займемся, а также еще некоторые навыки. Если вы уже сейчас хотите использовать функцию PostMail(), можете сразу открыть часть V книги и скопировать оттуда ее исходный код на PHP.

Глава 21

Работа с WWW

Мы уже рассматривали основы протокола HTTP в части I книги. Оператор echo, предназначенный для вывода данных в браузер, нам также хорошо знаком. Теперь осталось лишь разобрать, какие средства предусмотрены в PHP для работы с заголов-

ками HTTP.

Установка заголовков ответа

Первая функция, которую мы сейчас рассмотрим, — Header() — предназначена для посылки заголовка браузеру (точнее, для добавления заголовка к документу, пересылаемому браузеру). Она может быть вызвана только до первой команды вывода сценария (конечно, если вы до этого не воспользовались функцией буферизации ob_start()).

Вывод заголовка

int Header(string $string)

Обычно функция Header() является одной из первых команд сценария. Она предназначена для установки заголовков ответа, которые будут переданы браузеру — по одному заголовку на вызов. Не забывайте, что вызов Header() обязательно должен осуществляться до любого оператора вывода в сценарии — в противном случае вы получите предупреждение. Заметьте, что текст вне <? и ?> также рассматривается как оператор вывода, а потому старайтесь не делать лишних пробелов до первой "скобки" <? в сценарии (и в особенности в файле, который этим сценарием включается) и, конечно, после последнего ограничителя ?> во включаемом файле. Впрочем, вы сможете легко обнаружить подобную ошибку, если выставите уровень контроля ошибок, равный 15 (1+2+4+8) — в этом случае при недопустимом вызове Header() вы получите предупреждение. Пример:

//перенаправляет браузер на сайт PHP Header("Location: http://www.php.net");

//теперь принудительно завершаем сценарий, ввиду того, что после

//перенаправления больше делать нечего

exit;

290

Часть IV. Стандартные функции PHP

Запрет кэширования

Еще одно полезное приложение функции Header() — запрет кэширования документа браузером и Proxy-серверами. Большинство сценариев формируют документы, которые при каждом запуске программы изменяются. Очевидно, если браузер пользователя начнет кэшировать такие документы, ничего хорошего не получится. Выход — использовать в начале сценария следующие команды:

Header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");

// Дата в прошлом

Header("Cache-Control: no-cache, must-revalidate");

// HTTP/1.1

Header("Pragma: no-cache");

// HTTP/1.0

Header("Last-Modified: ".gmdate("D, d M Y H:i:s")."GMT");

Самое неприятное то, что для полного запрета кэширования приходится всегда посылать 4 указанных заголовка, и ни один пропустить нельзя — в противном случае не сработает либо браузер, либо Proxy-сервер. Так что рекомендую оформить их все в виде функции (например, с именем NoCache()) и затем вызывать эту функцию в нужный момент.

Получение заголовков запроса

Для получения всех заголовков запроса (того самого запроса, что вынудил запуститься сценарий) следует воспользоваться функцией GetAllHeaders():

array GetAllHeaders()

Функция GetAllHeaders() возвращает ассоциативный массив, содержащий данные о HTTP-заголовках запроса клиента, породившего запуск сценария. Ключи массива содержат названия заголовков, а значения — их величины.

Вот пример использования этой функции:

$headers = GetAllHeaders(); foreach($headers as $header=>$value)

echo "$header: $value<br>\n";

Функция GetAllHeaders() поддерживается PHP только в том случае, если он установлен в виде модуля Apache. В противном случае этой функции про- сто не будет (да и не может быть, потому что обычный CGI-сценарий не имеет доступа к заголовкам запроса). В частности, в PHP для Windows (который ча- ще всего реализуют именно в виде сценария) функция GetAllHeaders() не- доступна.

Не стоит увлекаться вызовами GetAllHeaders(): часто интересующую информацию (такую, например, как название браузера) можно получить и через переменные

Глава 21. Работа с WWW

291

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

Работа с Cookies

Я не буду здесь особо вдаваться в подробности работы с Cookies (хотя, положа руку на сердце, вдаваться тут особо не во что), тем более, что этот материал мы с вами уже рассматривали в части I книги. Повторю лишь основное.

Немного теории

Итак, Cookie — это именованная порция (довольно небольшая) информации, которая может сохраняться прямо в настройках браузера пользователя между сеансами. Причина, по которой применяются Cookies — большое количество посетителей вашего сервера, а также нежелание иметь нечто подобное базе данных для хранения информации о каждом посетителе. Поиск в такой базе может очень и очень затянуться (например, при цифре миллион гостей в день он будет отнимать львиную долю времени), и, в то же время, нет никакого смысла централизованно хранить столь отрывочные сведения. Использование Cookies фактически перекладывает задачу на плечи браузера, решая одним махом как проблему быстродействия, так и проблему большого объема базы данных с информацией о пользователе.

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

У каждого Cookie есть определенное время жизни, по истечение которого он автоматически уничтожается. Существуют также Cookies, которые "живут" только в течение текущего сеанса работы с браузером (это могут быть, например, имя и пароль, введенные при авторизации), или же идентификатор сессии (см. главу 25).

Каждый Cookie устанавливается сценарием на сервере. Для этого он должен послать браузеру специальный заголовок вида:

Set-cookie: данные

Однако в PHP этот процесс скрыт за функцией SetCookie(), которую мы сейчас рассмотрим, так что нам нет смысла вдаваться в детали.

Пожалуй, из теории осталось только добавить, что сценарии с других серверов, а также расположенные в другом каталоге, не будут извещены о "чужих" Cookies. Для них их как словно и нет. И это правильно с точки зрения безопасности — кто знает, насколько секретна может быть информация, сохраненная в Cookies? А вдруг там хранится номер кредитной карточки или пароль доступа к Пентагону?..

292

Часть IV. Стандартные функции PHP

Установка Cookie

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

int setcookie(string $name [,string $value] [,int $expire] [,string $path] [,string $domain] [,book $secure])

Вызов SetCookie() определяет новый Cookie, который тут же посылается браузеру вместе с остальными заголовками. Все аргументы, кроме имени, необязательны. Если задан только параметр $name (имя Cookie), то Cookie с указанным именем у пользователя удаляется. Вы можете пропускать аргументы, которые не хотите задавать, пустыми строками "". Аргументы $expire и $secure, как мы видим, не могут быть представлены строками, а потому вместо пустых строк здесь нужно использовать 0. Параметр $expire задает timestamp, который, например, может быть сформирован функциями time() или mktime(). Параметр $secure говорит о том, что величина Cookie может передаваться только через безопасное HTTPS-соединение (мы не будем рассматривать в этой книге HTTPS, о нем можно написать целые тома, что, вообще говоря, и делается). Вот несколько примеров использования SetCookie():

//Cookie на одну сессию, т. е. до закрытия браузера

SetCookie("TestCookie","Test Value");

//Эти Cookies уничтожаются браузером через 1 час после установки

SetCookie("TestCookie",$val,time()+3600); SetCookie("TestCookie",$val,time()+3600,"/~rasmus/",".utoronto.ca",1);

После вызова функции SetCookie() только что созданный Cookie сразу появляется среди глобальных переменных как переменная с заданным в параметре $name именем. Она появится и при следующем запуске сценария — даже если SetCookie() в нем и не будет вызвана. Параметр $value автоматически URL-кодируется при посылке на сервер, а при получении Cookie — автоматически декодируется, как это происходит и с данными формы, так что нам не нужно об этом заботиться.

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

Листинг 21.1. Индивидуальный счетчик посещений

if(!isSet($Counter)) $Counter=0; $Counter++; SetCookie("Counter",$Counter,0x7FFFFFFF);

echo "Вы запустили этот сценарий $Counter раз!";

Глава 21. Работа с WWW

293

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

Возможно, вам понадобится сохранять в Cookies не только строки, но и сложные объекты. Для этой цели объект нужно сначала преобразовать в строку (например, при помощи Serialize()) и поместить ее в Cookie. А потом, наоборот, распаковать строку, используя Unserialize().

Однако, если сохраняемый массив имеет небольшой размер, каждый его элемент можно разместить в отдельном Cookie:

SetCookie("Arr[0]","aaa");

SetCookie("Arr[1]","bbb");

SetCookie("Arr[2][0]","ccc"); // многомерный массив

По сути, Cookie с именем Arr[0] ничем не отличается с точки зрения браузера и сервера от обычного Cookie. Зато PHP, получив Cookie с именем, содержащим квадратные скобки, поймет, что это на самом деле элемент массива, и создаст его (массив). Тут нет ничего удивительного — ведь PHP поступает точно так же и с переменными, поступившими из формы пользователя... Правда, в отличие от форм, не советую вам особо увлекаться подобными Cookies: дело в том, что в большинстве браузеров число Cookies, которые могут быть установлены одним сервером, ограничено, причем ограничено именно их количество, а не суммарный объем. Поэтому, наверное, лучше будет все-таки воспользоваться функцией Serialize() и установить один Cookie, а еще лучше — написать собственную функцию сериализации, которая упаковывает данные чуть эффективнее.

Получение Cookie

Еще кое-что о Cookies. Предположим, сценарий отработал и установил какой-то Cookie, например, с именем Cook и значением Val. В следующий раз при запуске этого сценария (на самом деле, и всех других сценариев, расположенных на том же сервере в том же каталоге или ниже по дереву) ему передастся пара типа Cook=Val (через специальную переменную окружения). PHP это событие перехватит и автоматически создаст переменную $Cook со значением Val. То есть интерпретатор действует точно так же, как если бы значение нашего Cookie пришло откуда-то из формы. Та переменная, которую мы установили в прошлый раз, будет доступна и сейчас!

SSI и функция virtual()

Как известно, для одного и того же документа в Apache нельзя применять два "обработчика". Иными словами, действует принцип (по крайней мере, в Apache первого поколения): либо PHP, либо SSI (Server-Side Includes — Включения на стороне серве-

294

Часть IV. Стандартные функции PHP

ра). Однако в PHP имеются определенные средства для "эмуляции" SSI-конструкции include virtual.

Конструкция include virtual загружает файл, URL которого указан у нее в параметрах, обрабатывает его нужным обработчиком и выводит в браузер. То есть все происходит так, будто указанный URL был затребован виртуаль- ным браузером. Большинство SSI-файлов ограничиваются использованием этой возможности.

int virtual(string $url)

Функция virtual() представляет собой процедуру, которая может поддерживаться только в случае, если PHP установлен как модуль Apache. Она делает то же самое, что и SSI-инструкция <!--#include virtual=...-->. Иными словами, она генерирует новый запрос серверу, обрабатываемый им обычным образом, а затем выводит данные в стандартный поток вывода.

Чаще всего функция virtual() используется для запуска внешних CGI-сценариев, написанных на другом языке программирования, или же для обработки SSI-файлов более сложной структуры. В случае, если запускается сценарий, он должен генерировать правильные HTTP-заголовки, иначе будет выведено сообщение об ошибке. Заметьте, что для включения обычных PHP-файлов с участками кода функция virtual() неприменима — это выполняет оператор include.

Эмуляция функции virtual()

Функция virtual() работает только в том случае, если PHP установлен как модуль Apache. Проблемы начинаются, если это не так, и какой-то уже готовый сценарий интенсивно использует вызовы virtual(). Тогда мы должны будем либо переделать сценарий, либо написать эмуляцию для функции virtual() (благо в "сценарном" варианте PHP эта функция отсутствует, так что можно без оглядки на ключевые слова создать процедуру с именем virtual()). Вот как мы здесь поступим:

if(!function_exists("virtual")) { // Условно определяемая функция function Virtual($url)

{//* здесь должен идти код для преобразования относительного

//* URL (заданного относительно текущего каталога) в абсолютный. //* Мы не будем сейчас останавливаться на этом вопросе — оставим //* его для 5-й части книги.

global $HTTP_HOST,$SERVER_PORT; $f=@fopen("http://$HTTP_HOST:$SERVER_PORT$url","r"); if(!$f) {

Глава 21. Работа с WWW

295

echo "[an error ocurred while processing this directive: $url]"; return false;

}

// Теперь просто читаем все и выводим с помощью echo while(($s=fread($f,10000))!="") echo $s; fclose($f);

}

}

Обращаю ваше внимание на то, что используется не обычный fopen(), а сетевая его разновидность, на что указывает префикс http:// в имени файла. Единственное здесь сложное место — преобразование относительного URL в абсолютный. Но эта задача, конечно, вполне разрешима, и мы займемся ей уже скоро — в пятой части книги — наряду с остальными проблемами прикладного характера.

Глава 22

Основы регулярных выражений в формате

RegEx

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

Начнем с примеров

На мой взгляд, проще всего разбираться с регулярными выражениями на примерах. Так мы и поступим, если вы не против. Ведь вы не против?..

Пример первый

Наверняка вам приходилось когда-нибудь сталкиваться с такой ситуацией (а если не приходилось, то просто представьте ее себе): программа обрабатывает ка- кой-то входной файл с именем и расширением, и необходимо сгенерировать выходной файл, имеющий то же имя, но другое расширение. Например, файл file.in ваша программа должна обработать и записать результат в file.out. Проблема заключается в том, чтобы отрезать у имени входного файла все после точки и "приклеить" на это место out.

Проблема довольно тривиальна, и даже на PHP ее можно решить всего несколькими командами. Например, так:

$p=strrpos($inFile,'.');

if($p) $outFile=substr($inFile,0,$p); else $outFile=$inFile; $outFile.=".out";

На самом деле, выглядит довольно неуклюже (особенно из-за того, что приходится обрабатывать случаи, когда входной файл не имеет расширения, а значит, в нем нет точки). И эта "навороченность" имеет место, несмотря на то, что само действие приведенных строк можно описать всего несколькими словами. А именно: "Замени в

Глава 22. Основы регулярных выражений в формате RegEx

297

строке $inFile все, что после последней точки (и ее саму), или, в крайнем случае, "конец строки" на строку .out, и присвой это переменной $outFile".

Пример второй

Давайте теперь рассмотрим другой пример. Нам нужно разбить полное имя файла на две составляющие: каталог, в котором расположен файл, и само имя файла. Как мы знаем, для этого в PHP встроены функции basename() и dirname(), рассмотренные выше. Но предположим для тренировки, что их нет. Вот как мы реализуем требуемые действия:

$slash1=strrpos($fullPath,'/'); $slash2=strrpos($fullPath,'\\'); $slash=max($slash1,$slash2); $dirName=substr($fullPath,0,$slash); $fileName=substr($fullPath,$slash+1,10000);

Здесь мы воспользовались тем фактом, что strrpos() возвращает false, которое интерпретируется как 0, если искомый символ не найден. Обратите внимание на то, что пришлось два раза вызывать strrpos(), потому что мы не знаем, какой слэш будет получен от пользователя — прямой или обратный. Видите — код все увеличивается. И уменьшить его почти невозможно.

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

Опять же, сформулируем словами то, что нам нужно: "Часть слова после последнего прямого или обратного слэша или, в крайнем случае, после начала строки, присвой переменной $fileName, а "начало строки" — переменной $dirName". Формулировку "часть слова после последнего слэша" можно заменить на несколько другую: "Часть слова, перед которой стоит слэш, но в нем самом слэша нет".

Выводы

Скорее всего, вы уже поняли, что основной акцент в этих примерах я старался делать не на алгоритмах, а на "словесных утверждениях". Они состояли из довольно несложных, но комплексных частей, относящихся не к одному символу (как это произошло бы, организуй мы цикл по строке), а сразу к нескольким "похожим"...