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

Самоучитель по PHP 4

.pdf
Скачиваний:
85
Добавлен:
02.05.2014
Размер:
4.36 Mб
Скачать

Глава 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". Формулировку "часть слова после последнего слэша" можно заменить на несколько другую: "Часть слова, перед которой стоит слэш, но в нем самом слэша нет".

Выводы

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

298

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

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

Терминология

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

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

Итак, мы имеем выражение и мы имеем строку. Операцию проверки, удовлетворяет ли строка этому выражению (или выражение — строке, как хотите) условимся называть сопоставлением строки и выражения. Если какая-то часть строки успешно сопоставилась с выражением, мы назовем это совпадением. Например, совпадением от сопоставления выражения "группа букв, окруженная пробелами" к строке "ab cde fgh" будет строка "cde" (ведь только она удовлетворяет нашему выражению). Возможно, дальше мы с этим совпадением захотим что-то проделать — например, заменить его на какую-то строку или, скажем, заключить в кавычки. Это — типичный пример со- поставления с заменой. Все эти возможности реализуются в PHP в виде функций, которые мы сейчас и рассмотрим.

Использование регулярных выражений в PHP

Вернемся на минуту опять к практике. Любое регулярное выражение в PHP — это просто строка, его содержащая, поэтому функции, работающие с регулярными выражениями, принимают их в параметрах в виде обычных строк.

Сопоставление

bool ereg(string $expr, string $str [,list &$Matches])

Функция пытается сопоставить выражение $expr строке $str и в случае удачи возвращает true, иначе — false. Если совпадение было найдено, то в список $Matches (конечно, если он задан) записываются отдельные участки совпадения (как выделять эти участки на языке RegEx, мы рассмотрим немного позже). Пока скажу только, что в $Matches[0] всегда будет возвращаться подстрока совпадения целиком.

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

299

Сопоставление с заменой

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

string ereg_replace(string $expr, strint $str, string $strToChange)

Эта функция занимается тем, что ищет в строке $str все подстроки, соответствующие выражению $expr, и заменяет их на $strToChange. В строке $strToChange могут содержаться некоторые управляющие символы, позволяющие обеспечить дополнительные возможности при замене. Их мы рассмотрим позже, а сейчас скажу только, что сочетание \0 (в PHP эта строка будет записываться как "\\0") будет заменено на найденное совпадение целиком.

Язык RegEx

Существует несколько разновидностей языков, используемых для записи регулярных выражений и работы с ними. У всех них есть много общего, но отдельные части все же отличаются. В PHP версии 3 стандартно реализован только один из языков — он называется RegEx.

В четвертой версии интерпретатора поддерживается также и стандарт PCRE (Perl-Compatible Regular Expression — Регулярные выражения языка Perl). Его выражения несколько более универсальны. Прочитать о формате PCRE можно в любой книге по Perl.

Нужно заметить, что стандарт RegEx — один из самых первых и простых, поэтому он не включает некоторые возможности, которые могут иногда потребоваться.

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

rпростые символы, а также управляющие символы, играющие роль их "заменителей";

rуправляющие конструкции (квантификаторы повторений, оператор альтернативы, группирующие скобки и т. д.);

rтак называемые мнимые символы (в строке их нет, но, тем не менее, они как бы помечают какую-то часть строки — например, ее конец).

300

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

Простые символы

Класс простых символов, действительно, самый простой. А именно, любой символ в строке на RegEx обозначает сам себя, если он не является управляющим (к управляющим символам причисляются следующие: ".*?+[]{}|$^"). Например, регулярное выражение abcd будет "реагировать" на строки, в которых встречается последовательность abcd.

Отмена действия спецсимволов

Если же нужно вставить в выражение один из управляющих символов, но только так, чтобы он "не действовал", достаточно предварить его обратным слэшем. К примеру, если мы ищем строку, содержащую подстроку a*b, то мы должны использовать для этого выражение a\*b (опять же, в PHP эта строка будет записываться как "a\\*b"), поскольку символ * является управляющим (вскоре мы рассмотрим, как он работает).

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

$a="a\*b"

но можем

$a="a\\*b"

В последнем случае в строке $a оказывается a\*b. Так как регулярные выражения в PHP представляются именно в виде строк, то необходимо постоянно помнить это правило.

Ошибки такого рода чрезвычайно распространены, и можно не один час ло- мать голову, почему же все работает не так, как должно.

Группы символов

Было бы глупо, если бы RegEx позволял нам задавать части искомых строк только непосредственно, как это было рассмотрено выше. Поэтому существуют несколько спецсимволов, обозначающих сразу группу букв. Эта возможность — один из краеугольных камней, основ регулярных выражений. Самый важный из таких знаков — точка "." — обозначает один любой символ. Например, выражение a.b имеет совпадение для строк azb или aqb, но не "срабатывает" для, скажем, aqwb или ab. Позже мы рассмотрим, как можно заставить точку обозначать ровно один (или, к примеру, ровно пять) любых символов.

Но это далеко не все. Возможно, вы захотите искать не любой символ, а один из нескольких указанных. Для этого наши символы нужно заключить в квадратные скобки. К примеру, выражение a[xXyY]c соответствует строкам, в которых есть подстро-