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

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

.pdf
Скачиваний:
99
Добавлен:
24.05.2014
Размер:
4 Мб
Скачать
☆

408

Часть V. Приемы программирования на PHP

Но как же сопоставить идентификатор обработчика тому сценарию, который содержит его код? У сервера Apache для этого есть специальная директива под названием Action. Где задается эта директива? В любом файле конфигурации Apache. Конечно, удобнее всего это делать в файле .htaccess, расположенном в корневом каталоге виртуального хоста, чтобы изменения распространились сразу на весь сервер. Мы уже рассматривали такую стратегию выше, только теперь все будет чуточку сложнее.

Вот требуемые директивы. Поместим их, как водится, в главный .htaccess-файл хоста.

#Сначала связываем имя обработчика с конкретным файлом.

#Знак "?" говорит серверу, что исходный URL запроса следует

#передать сценарию методом GET, т. е. через QUERY_STRING. Action libhandler "/lib/libhandler.php?"

#Теперь уведомляем сервер, документы какого типа мы желаем

#"пропускать" через наш обработчик.

AddHandler libhandler .html .htm

В этом фрагменте есть два тонких места.

rПуть к сценарию обработчика всегда указывается как абсолютный URL без указания имени хоста и порта. Мы не можем задать здесь ни путь к файлу, ни даже относительный URL. По той причине, чтобы позволить одному обработчику "передавать эстафету" другому. В самом деле, ведь это и происходит в нашем примере: Apache сначала определяет, что документ нужно "пропустить" через обработчик libhandler, а т. к. он представляет собой ни что иное, как сценарий на PHP, то и через интерпретатор PHP. В деталях затронутый процесс чуть сложнее, но мы не будем в него углубляться.

rПосле URL сценария в директиве Action следует знак ?. Зачем он? Это связано с механизмом, который использует Apache для того, чтобы определить конечный обработчик для того или иного документа. Когда пользователь посылает серверу URL, который, как Apache "знает", подходит под одну из команд Action, к этому URL слева просто присоединяется второй параметр директивы, и все начинается сначала — до тех пор, пока не будет найден последний обработчик. Например, если пользователь ввел /dir/file.html, то благодаря директиве Action указан-

ный адрес преобразуется в /lib/libhandler.php?/dir/file.html. Это — ни что иное, как адрес PHP-сценария с параметром, который будет передан программе, как обычно, через переменную окружения QUERY_STRING.

Теперь сервер знает, что все документы с расширением html и htm нужно обрабатывать при помощи сценария, расположенного по адресу /lib/libhandler.php. Точнее, при каждой попытке открыть страницы с указанными расширениями Apache будет запускать наш сценарий и в числе обычных переменных окружения отправлять ему несколько специальных, содержащих первичную информацию о запросе, переданном пользователем. Мы сейчас рассмотрим эти переменные на практике. Если вас интересует их полный список, попробуйте распечатать массив $GLOBALS или вос-

Глава 29. Модульность программы. Написание "библиотекаря"

409

пользоваться функцией phpinfo(), вставив ее первой и единственной командой об-

работчика libhandler.php.

Вы, возможно, спросите, почему же мы не добавили в список расширений, на которые будет "реагировать" сценарий, еще одно — php? Давайте посмотрим, что бы произошло, поступи мы так. Предположим, пользователь хочет загру- зить страницу /a.php. Apache "видит", что расширение у нее — php, и запус- кает обработчик с именем /lib/libhandler.php. Точнее, он "сваливает" всю работу на сценарий libhandler.php. Еще точнее — перенаправляет

сервер по адресу /lib/libhandler.php?a.php! И тут же зацикливается,

потому что у этого сценария расширение — также php. Итак, сервер начинает вызывать сценарий снова и снова, все удлиняя его URL — до тех пор, пока не "поймет": что-то неверно, и пора аварийно завершаться, о чем и сообщает в файлах журнала. О том, как решить эту проблему, рассказано в самом конце главы.

Ну вот, у нас уже почти все готово. Осталось только написать сам код обработчика. Это не так уж и сложно. Но прежде давайте вспомним, зачем мы вообще связались с обработчиками. Для автоматической загрузки библиотекаря перед выполнением того или иного сценария, помните? Что же, вот пример (листинг 29.5).

Мы подразумеваем, что обработчик libhandler.php находится в том же са- мом каталоге, что и библиотекарь с большинством модулей. Это довольно удобно, поскольку позволяет нам задавать путь к каталогу с модулями лишь в единственном месте — в директиве Action файла .htaccess, да и то в виде относительного URL. Оцените, насколько это проще для будущих модифика- ций сайта.

Листинг 29.5. Обработчик /lib/libhandler.php с подключением библиоте- каря

<?

//Прежде всего, устанавливаем свои каталоги поиска модулей.

//Это, по нашей договоренности, — текущий в данный момент каталог. $INC[]=getcwd();

//Проверяем, не пытается ли пользователь запустить обработчик напрямую,

//минуя Apache — например, путем набора в браузере адреса

///lib/libhandler.php. Так как адрес, введенный пользователем,

//всегда передается в переменной окружения REQUEST_URI, то нужно

//"бить тревогу", если переданная строка адреса встречается

410

Часть V. Приемы программирования на PHP

//в имени файла обработчика (причем в любом регистре символов).

//Мы не забыли отрезать в этой строке часть после ?, потому что

//она будет мешать при сравнении с именем файла.

//К сожалению, похоже, это единственный переносимый между операционными

//системами способ проверки легальности запуска обработчика.

$FileName=strtr(__FILE__,"\\","/"); $ReqName=ereg_Replace("\\?.*","",strtr(getenv("REQUEST_URI"),"\\","/")); if(eregi(quotemeta($ReqName),$FileName)) {

//Выводим сообщение об ошибке include "libhandler.err";

//Записываем в журнал данные о пользователе $f=fopen("libhandler.log","a+");

fputs($f,date("d.m.Y H:i.s")." $REMOTE_ADDR - Access denied\n"); fclose($f);

//Завершаем работу

exit;

}

//Все в порядке — корректируем переменные окружения в соответствии

//с запрошенным пользователем адресом.

@putenv("REQUEST_URI=". $GLOBALS["HTTP_ENV_VARS"]["REQUEST_URI"]= $GLOBALS["REQUEST_URI"]= getenv("QUERY_STRING")

); @putenv("QUERY_STRING=".

$GLOBALS["HTTP_ENV_VARS"]["QUERY_STRING"]= $GLOBALS["QUERY_STRING"]= ereg_Replace("^[^?]*\\?","",getenv("QUERY_STRING"))

);

//Подключаем библиотекарь include "librarian.phl";

//Здесь можно выполнить еще какие-нибудь действия...

//. . .

//Запускаем тот сценарий, который был запрошен пользователем chdir(dirname($SCRIPT_FILENAME));

include $SCRIPT_FILENAME; ?>

Глава 29. Модульность программы. Написание "библиотекаря"

411

Ну и, конечно, какая же программа обходится без вывода диагностических сообщений? Наш пример подгружает файл libhandler.err в случае "жульничества" пользователя. Наверное, в нем следует написать что-то типа:

<head><title>Доступ запрещен!</title></head> <body>

<h2>Доступ запрещен!</h2>

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

</body>

В результате мы пришли к тому, что теперь все документы с расширениями html и htm рассматриваются как сценарии на PHP. Они запускаются уже после того, как подключен библиотекарь, так что могут пользоваться функцией Uses().

Если вы не собираетесь использовать библиотекарь, а хотите применять опи- санный выше механизм только для того, чтобы включить PHP для файлов с расширением html, лучше прочитайте конец этой главы. Там описано, как сде- лать это проще.

Перехват обращений к несуществующим страницам

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

/forum/Computers-01-04-01.html

Хотя файла Computers-01-04-01.html нет и в помине, обработчик может перехватить запрос к нему и определить, что речь идет о новостях в разделе "Компьютеры" за 1 апреля 2001 года. Затем, получив нужную информацию из базы данных, остается лишь отправить ее клиенту.

Обычно для подобных целей используют специальный модуль Apache — mod_rewrite. К сожалению, по статистике далеко не все хостинг-провайдеры

412

Часть V. Приемы программирования на PHP

соглашаются устанавливать его на свои серверы. В то же время механизм ActionAddHandler работает всегда и везде, где установлен Apache.

Надо заметить, что в примере из листинга 29.5 мы никак не перехватываем обращения к несуществующим страницам. Что происходит, если пользователь все же введет неправильный адрес? Очевидно, вызов include, стоящий в предпоследней строчке, завершится неуспешно, а PHP выведет сообщение об ошибке. Наверное, в реальной программе нужно как-то обрабатывать эту ситуацию, — например, при помощи проверки существования запрошенного файла.

Связывание PHP с другим расширением

Как мы знаем, сам PHP представляет собой обычный обработчик. Значит, скажете вы, чтобы заставить его обрабатывать документы с расширением, отличным от PHP, нам нужно просто добавить директиву AddHandler для этого расширения в соответствующий файл .htaccess? Не совсем. Проблема заключается в том, что мы не знаем идентификатора обработчика, он хранится где-то в недрах кода интерпретатора. Вместо этого мы поступим по-другому: заставим Apache считать, что документы с нужным нам расширением имеют тот же тип, что и с расширением php.

Что же такое тип документа? Это еще одно понятие, которое использует Apache в своей работе. Некоторые из этих типов также "понимают" и браузеры. В их числе, например, text/html, обозначающий HTML-страницу, image/gif, который сигнализирует, что данные являются рисунком GIF, и т. д. Именно этими типами (а не расширениями страниц!) руководствуются браузеры, когда решают, в каком формате прислал сервер данные.

Однако есть несколько типов документов, которые никогда не отсылаются браузеру в исходном виде. Один из них — application/x-httpd-php. Именно с этим типом и связан интерпретатор PHP. Если сервер "видит", что пользователь запросил страницу, которая имеет тип application/x-httpd-php, он активизирует PHP, а уж тот берет на себя всю дальнейшую ответственность по запуску сценария и выводу "правильного" заголовка типа (чаще всего text/html) в браузер.

Как же сервер узнает, какой тип имеет тот или иной документ? Вообще говоря, это отдельная проблема. Самое простое ее решение — определять тип по расширению файла. В большинстве случаев это оказывается самым лучшим решением. Программист может сам задать, какое расширение соответствует тому или иному типу, добавив в нужный файл .htaccess следующую директиву:

AddType имя_типа расширение1 расширение2 …

А как быть, если многие из наших документов не имеют в принципе никакого расширения? Например, мы хотим хранить рисунки GIF, JPG и PNG в файлах без расширения. Разумеется, в этом случае директива AddType нам не помо-

Глава 29. Модульность программы. Написание "библиотекаря"

413

жет. Однако у Apache существует еще одно мощное средство для распознава- ния типов страниц — это модуль mod_mime_magic (конечно, если он подклю- чен к той версии сервера, которая установлена у вашего хостинг-провайдера). В случае, если определение типа на основе директив AddType закончилось неудачей, этот модуль пытается по нескольким первым байтам файла узнать, какого же он типа. Например, во всех GIF-файлах первые три байта — симво- лы G, I и F. Поэтому с вероятностью практически 100% определение типа про- ходит правильно.

Предположим, что мы хотим связать расширение php4 с PHP для всего сайта. Для этого запишем в файл .htaccess, расположенный в корневом каталоге сервера, такую директиву:

AddType application/x-httpd-php php4

Теперь для всех файлов с расширением php4 будет выполняться то же, что и для php. Кстати говоря, именно такая директива (но для php) записана в главном файле httpd.conf вашего хостинг-провайдера.

Решение проблемы зацикливания обработчика

Помните, обработчик из листинга 29.5 мы связали только с расширениями html и htm, но не php? Мы сделали это, чтобы избежать зацикливания обработчика (см. со- ответствующее замечание). Давайте исправим положение. Очевидно, нужно связать с PHP еще одно расширение, которое не будет использоваться в сайте нигде, кроме как в имени обработчика из листинга 29.5. Пусть это будет, например, php4. Модифицируем наш .htaccess:

#Связываем расширение php4 с PHP AddType application/x-httpd-php php4

#Замкнем имя обработчика на конкретный файл

Action libhandler "/lib/libhandler.php4?"

#Документы этого типа мы желаем "пропускать" через наш обработчик

AddHandler libhandler .html .htm .php

Ну и, конечно, осталось только переименовать имеющийся у нас файл libhandler.php в libhandler.php4.

Теперь все сценарии с расширением php могут использовать функции, предоставляемые библиотекарем.

Глава 30

Код и шаблон страницы

Что и говорить, конечно, очень удобно, что PHP позволяет комбинировать код программы с обычным HTML-текстом, но этой возможностью все же не стоит злоупотреблять. И особенно в больших сценариях. Это чередование очень плохо смотрится: сначала код, потом — вставки HTML, а затем — опять код. Кроме того, вашему HTML-верстальщику будет крайне трудно понять, где же в этом сценарии именно "его" участки, которые он может править и изменять.

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

Думаете, сейчас мы будем углубляться в "дебри теории", далекой от практики и вряд ли вам полезной? Ничего подобного. Я просто расскажу, как можно удобно строить свои программы, а в конце приведу довольно "внушительный" код шаблонизатора (так я называю систему управления страницами и шаблонами), который призван сделать работу Web-программиста максимально простой и эффективной.

Некоторые программисты утверждают, что отделению кода от шаблона стра- ницы уделяют слишком много внимания — чрезмерно много. Если и вы так думаете, — что же, я не буду с вами спорить и критиковать вашу точку зрения. Если бы я не занимался этой проблемой столько времени, то, возможно, и сам бы так считал. Будем честны: отвечает ли проблема отделения кода от шабло- на страницы тому вниманию и количеству страниц, что я ей здесь уделил? От- кровенно говоря, не отвечает. В действительности, чтобы полностью расска- зать о возможных решениях задачи, потребовалось бы написать отдельную книгу размером в тысячу страниц. Я же ограничусь всего кое-какими рассужде- ниями и примером простейшего шаблонизатора.

416

Часть V. Приемы программирования на PHP

Идеология

Большинство сценариев пишутся на различных языках программирования без всякого отделения кода от шаблона страницы. Зачем же тогда нам это нужно? Что заставляет нас искать новые пути в Web-программировании?

Причина всего одна. Это — желание поручить разработку качественного и сложного сценария сразу нескольким людям, чтобы каждый из них занимался своим делом, которое, как предполагается, он знает лучше всего. Одна группа людей (назовем ее "программисты") занимается тем, что касается взаимодействия программы с пользователем и обработки данных. Другая же группа (для простоты я буду говорить о ней как о "дизайнерах"), наоборот, отвечает лишь за эстетическую часть работы. Разумеется, программисты и дизайнеры — не единственные категории, которые нужно сформировать при создании крупных сайтов. Безусловно, требуется еще одно лицо, которое бы "связывало" и координировало их между собой. Им может быть человек, не имеющий выдающихся достижений ни в Web-дизайне, ни в Webпрограммировании, но в то же время наделенный хорошей интуицией и знаниями. Если этого человека нет, кому-то все равно придется выполнять его работу (например, одному из программистов), что, конечно же, будет немного противоречить желаниям последнего. В результате работа над проектом затянется и, возможно, "обрастет" излишними сложностями технического характера.

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

Зачем нам вообще понадобилось распределять разработку Web-сценариев по нескольким направлениям? Отвечаю последовательно. Во-первых, так создаются гораздо более качественные программы и Web-страницы. Во-вторых, сроки выполнения работы значительно сокращаются за счет организации параллельного выполнения задания. Если вас это все равно не убедило, вспомните о том, что именно так организуются практически все крупные Web-студии по всему миру.

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

Глава 30. Код и шаблон страницы

417

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

Двухуровневая схема

Итак, мы желаем максимально отделить работу программистов и дизайнеров. Давайте будем делать это не сразу, а постепенно, детализируя ситуацию. Вначале решим более простую проблему: разделим код сценария и шаблон его страницы (что я называю двухуровневой схемой построения сценария). Это довольно несложно. Мы уже поступали так в главе 28, когда писали сценарий простейшего фотоальбома. Теперь мы поставим задачу более точно.

Шаблон страницы

Пусть нам нужно завести новый раздел сайта — гостевую книгу. Выделим для нее отдельный каталог на сервере и создадим в нем файл примерно следующего содержания (листинг 30.1). Назовем его шаблоном страницы.

Листинг 30.1. Шаблон: gbook.htm

<html><head><title>Гостевая книга</title></head> <body>

<h2>Добавьте свое сообщение:</h2> <form action=gbook.php method=post>

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

Комментарий:<br>

<textarea name="New[text]" wrap=virtual cols=60 rows=5></textarea><br> <input type=submit name="doAdd" value="Добавить!">

</form>

<h2>Гостевая книга:</h2> <?foreach($Book as $id=>$Entry) {?>

Имя человека: <?=$Entry['name']?><br>

Его комментарий:<br> <?=$Entry['text']?><hr> <?}?>