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

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

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

Глава 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> <?}?>

418

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

</body></html>

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

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

<foreach src=Book>

Имя человека: $name<br>

Его комментарий:<br>$text<hr> </foreach>

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

Хочу сразу сказать всем любителям разбивать один шаблон на множество файлов: их способ чаще всего не оправдывает себя при написании крупных сценариев. Дело в том, что при такой организации довольно тяжело перестав- лять подшаблоны внутри страницы. Кроме того, подшаблоны нужно как-то за- гружать, а поручать эту задачу коду страницы не очень удобно все из тех же соображений: придется работать и программисту, и верстальщику. Легче всего это представить на примере все той же гостевой книги: если бы мы выделили тело цикла foreach в отдельный файл и попытались избавиться от этой ин- струкции, то пришлось бы переложить задачу циклического вывода данных на плечи программиста, сообщив ему попутно имя подшаблона. Чувствуете, сколько лишних зависимостей?..

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

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

419

Генератор данных

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

Мы видим, что где-то должен быть скрыт весь этот код. Он, действительно, располагается в отдельном файле с именем gbook.php. Отличительная черта этого файла — то, что в нем нет никакого намека на то, как нужно форматировать результат работы сценария. Именно поэтому я называю его генератором данных (листинг 30.2).

Листинг 30.2. Генератор данных: gbook.php

<?

define("GBook","gbook.dat"); // имя файла с данными гостевой книги

//Загружает гостевую книгу с диска. Возвращает содержание книги. function LoadBook($fname)

{ $f=@fopen("gbook.dat","rb"); if(!$f) return array(); $Book=Unserialize(fread($f,100000)); fclose($f); return $Book;

}

//Сохраняет содержимое книги на диске.

function SaveBook($fname,$Book)

{$f=fopen("gbook.dat","wb"); fwrite($f,Serialize($Book)); fclose($f);

}

//Исполняемая часть сценария.

//Сначала — загрузка гостевой книги. $Book=LoadBook(GBook);

//Обработка формы, если сценарий вызван через нее.

//Если сценарий запущен после нажатия кнопки Добавить...

if(!empty($doAdd)) {

//Добавить в книгу запись пользователя — она у нас хранится

//в массиве $New, см. форму в шаблоне. Запись добавляется,

//как водится, в начало книги.

$Book=array(time()=>$New)+$Book;

420

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

// Записать книгу на диск. SaveBook(GBook,$Book);

}

//Все. Теперь у нас в $Book хранится содержимое книги в формате:

//array (

//время_добавления => array(

//(или id) name => имя_пользователя,

//text => текст_пользователя

//),

//. . .

//);

//Вот теперь загружаем шаблон страницы.

include "gbook.htm"; ?>

Как видим, исполняемая часть довольно небольшая и, действительно, занимается лишь подготовкой данных для их последующего вывода в шаблоне. Шаблон рассматривается этой составляющей как обычный PHP-файл, который она подключает при помощи инструкции include. Ясно, что весь код шаблона (хотя его и очень мало) выполнится в том же контексте, что и генератор данных, а значит, ему будет доступна переменная $Book.

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

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

Обратите внимание: для того чтобы теперь переделать гостевую книгу так, чтобы она использовала базу данных, а не файл, достаточно изменить всего лишь 2 функции: LoadBook() и SaveBook(). Ни других частей генератора данных, ни, тем более, шаблона это не затронет. На самом деле, такой подход