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

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

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

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

421

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

Взаимодействие генератора данных и шаблона

Вернемся опять к тому же генератору данных. В нем мы проверяем, не запущен ли сценарий книги в ответ на нажатие кнопки Добавить в форме. Тут я хочу кое-что напомнить. Если вызвать программу без параметров, то пользователю будет просто выдано содержимое гостевой книги, в противном же случае (то есть при запуске из формы) осуществится добавление записи. Таким образом, мы "одним махом убиваем двух зайцев": используем один и тот же шаблон для двух разных страниц, внешне крайне похожих. Такую практику нужно только приветствовать, не правда ли? Определяем мы, нужно ли добавлять запись, по состоянию переменной $doAdd. Помните, именно такое имя имеет submit-кнопка в форме? Когда ее нажимают, сценарию поступает пара "doAdd=Добавить!", чем мы и воспользовались. Итак, если кнопка нажата, то мы вставляем запись в начало массива $Book и сохраняем его на диске.

Обратите внимание, насколько проста операция добавления записи. Так получилось вследствие того, что мы предусмотрительно дали полям формы с названием и текстом имена, соответственно, New[name] и New[text], которые PHP преобразовал в массив. Вообще говоря, придумывание таких имен для полей — задача как раз того "третьего лица", о котором я говорил выше. Это — работа скорее программистская, нежели дизайнерская (хотя, безусловно, от удачного планирования названий имен полей зависит не так уж и мало).

Подчеркиваю, что в самом коде генератора данных gbook.php в принципе не присутствует никаких данных о внешнем виде нашей гостевой книги. В нем нет ни одной строчки на HTML. Иными словами, генератору совершенно "все равно", как выглядит книга. Он занимается лишь ее загрузкой и обработкой. Это значит, что в будущем для изменения внешнего вида гостевой книги нам не придется править этот код, т. е. мы добились некоторого желаемого разделения труда дизайнера и программиста.

С другой стороны, шаблон gbook.htm не делает никаких предположений о том, как же именно хранится книга на диске и как она обрабатывается. Его дело — "красиво" вывести содержимое массива $Book, "и точка". К тому же он почти не содержит кода на PHP (разве что самый минимум, без которого никак не обойтись). А значит, дизайнеру будет легко изменять внешний вид книги.

Недостатки

У любой медали есть оборотная сторона и, как часто бывает, от ее качества зависит довольно много. Имеется она и у двухуровневой схемы построения сценариев. Давайте систематизируем все недостатки и постепенно будем их исправлять.

422

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

1.Что такое для пользователя "гостевая книга"? Конечно же, это прежде всего страница. А для разработчика сценария? Разумеется, программный код. Получается, что взгляды пользователя несколько отличаются от воззрений разработчика. Как разрешить сформулированную неувязку? Для этого нужно посмотреть на нашу систему "генератор данных — шаблон" со стороны. Что мы видим? Генератор данных загружает данные с диска, а затем обращается к шаблону, чтобы тот их вывел. Но пользователь хочет иметь перед глазами прежде всего шаблон, а не работу генератора! Мы же заставляем его запускать программу. Возможно, следующее положение и покажется спорным, но на практике оно полностью оправдывает

себя. А именно, предлагается поменять направление обмена данными между шаблоном и генератором данных. Пусть шаблон запрашивает данные у генератора, а тот их ему предоставляет. Согласитесь, это укладывается даже в замечательно зарекомендовавшую себя модель обмена "клиентсервер": шаблон — это клиент, а генератор данных — сервер.

2.Хотя шаблон двухуровневой схемы и является подчиненным элементом, он все же вынужден ссылаться на имя генератора данных через атрибут action тэга <form>. Конечно, это вносит лишь дополнительную неразбериху и является еще одним стимулом к замене понятий "главный" и "подчиненный".

3.Генератор данных состоит из излишне большого числа логических блоков, связанных лишь односторонне. В самом деле, если мы будем писать систему администрирования для нашей гостевой книги, нам опять понадобятся функции загрузки и сохранения данных (то есть, функции LoadBook() и SaveBook()). Поэтому логично будет выделить их в отдельный файл, который я здесь буду называть ядром сценария. Ядро — это третий компонент в трехуровневой схеме построения программы, о которой мы сейчас будем говорить. Разумеется, в сложных системах ядро может состоять из десятков (и даже сотен) файлов. Вообще говоря, оно также содержит и сведения о конфигурации (константу GBook), так что часто бывает удобно выделить эти данные в отдельный файл.

4.Шаблон страницы вмещает в себя весь ее HTML-код. В то же время, в современном мире подавляющее большинство сайтов организовано так, что их страницы построены по одной и той же "модели" (например, карта раздела слева, текст справа, баннер вверху, дополнительная информация cнизу и т. д.). Согласитесь, что копировать один и тот же шаблон в сотни мест просто неприемлемо для последующего редизайна (который, скорее всего, последует практически сразу, потому что при первой реализации довольно сложно бывает сразу учесть все пожелания заказчика). Конечно, мы можем вставить в нужные места шаблона вызовы инструкции include, загружающей соответствующие блоки страниц. Однако при детальном рассмотрении оказывается, что это всего лишь некоторая "отсрочка" неизбежной проблемы редизайна. В самом деле, мы сможем легко менять внешний вид отдельных блоков, но у нас не получится переставлять их в другом порядке (например, карта — справа, текст — слева) без утомительного изменения HTML-кода всех страниц.

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

423

r Пожалуй, пока достаточно. Сейчас мы попытаемся решить все эти проблемы, кроме последней (традиционно являющейся для Web-студий настоящим ящиком Пандоры), которой мы тоже вскоре займемся, что выльется, как вы увидите, в довольно внушительный объем кода.

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

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

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

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

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

Листинг 30.3. Шаблон: gbook.html

<?include "gbook.php"?> <html><head><title>Гостевая книга</title></head> <body>

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

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

. . .

Я не буду приводить текст страницы целиком, т. к. после определения формы он идентичен листингу 30.1. Итак, мы помещаем инструкцию include самой первой строчкой шаблона, и на это есть своя причина. Дело в том, что при различных, скажем так, "аварийных" событиях генератор данных может перенаправить браузер на другой адрес, не вернув управление в шаблон. Конечно, если бы include размещалась где-нибудь в середине шаблона, мы не смогли бы этого сделать, поскольку часть страницы могла быть уже отослана пользователю.

424

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

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

Заметьте, что шаблон имеет расширение HTML и выглядит для пользователя как обычная HTML-страница. Пользователь может и не подозревать, что в действительности сценарий написан на PHP. Но, чтобы описанный механизм заработал, нам необходимо связать расширение HTML с обработчиком PHP. Мы уже делали это в гла- ве 29. Вот какую строчку нужно добавить в файл .htaccess, расположенный в каталоге (или "надкаталоге") сценария:

AddHandler application/x-httpd-php .html

Мы должны использовать директиву AddHandler, а не AddType, на случай, если для расширения HTML был ранее установлен другой обработчик. Им мо- жет быть, например, SSI (Server-Side Includes — Включения на стороне серве- ра) или даже PHP версии 3. В этом случае директива AddType "не срабатыва- ет".

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

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

Диаграммы двухуровневой и трехуровневой моделей

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

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

425

иное, как переадресация на другую страницу, возможно, выполняемая генератором данных.

Генератор

Пользователь

данных

 

Шаблон

страницы

Рис. 30.1. Двухуровневая схема

Мы видим, что в случае двухуровневой схемы связи между компонентами сценария исключительно циклические (см. рис. 30.1). Каждая часть программы взаимодействует на равных с другой ее частью.

Легко заметить, что рис. 30.2 гораздо сложнее, чем рис. 30.1. Его "загруженность" объясняется тем, что трехуровневая схема более, чем это может показаться с первого взгляда, сложна и универсальна по сравнению с двухуровневой. Обратите внимание на то, что практически все связи стали двусторонними, а циклические — исчезли. Это позволяет работать блоком более независимо, чем для случая двухуровневой модели. А значит, работу над сценарием можно распределить по нескольким исполнителям более эффективно, — к чему мы и стремились.

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

426

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Шаблон

 

 

 

Интерфейсная

 

 

 

 

 

 

 

 

часть

 

 

 

Ядро

 

 

 

 

 

 

 

 

 

 

 

страницы

 

 

 

программы

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Конфигу-

 

 

 

 

 

 

 

 

 

 

 

 

рация

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Пользователь

Рис. 30.2. Трехуровневая схема

Интерфейс

Как можно заметить из листинга 30.4, интерфейс сценария гостевой книги стал гораздо проще, чем это было с генератором данных из листинга 30.2. Файл, в котором содержится его код, называется точно так же, как и файл генератора. Это и не удивительно: "снаружи" интерфейс выглядит как полноценный генератор данных, а о существовании ядра шаблон даже и не "подозревает".

Листинг 30.4. Интерфейс: gbook.php

<?

include "kernel.php"; // Загружаем ядро. $Book=LoadBook(GBook); // Загрузка гостевой книги. // Обработка формы, если сценарий запущен через нее. if(!empty($doAdd)) {

//Добавить в книгу запись пользователя. $Book=array(time()=>$New)+$Book;

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

}

//Загрузка шаблона не нужна — теперь, наоборот, шаблон

//вызывает интерфейс.

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

427

?>

Как видим, интерфейс занимается только той работой, для которой он и предназначен: выступает "посредником" между ядром и шаблоном. Самым первым загружается ядро — файл kernel.php (я люблю так его называть). Дальше осуществляется исключительно обработка и "расшифровка" входных данных и формирование выходных.

Ядро

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

Ядро в идеале должно содержать лишь набор функций, которые позволяют исчерпывающим образом работать с объектом программы. В этом смысле идеально его объ- ектно-ориентированное построение. Об объектно-ориентированном программировании на PHP будет вкратце рассказано в главе 31, а пока не будем усложнять и без того "скользкую" задачу и посмотрим, что представляет собой ядро нашей гостевой книги

(листинг 30.5).

Листинг 30.5. Ядро: kernel.php

<?

//Загружаем конфигурацию. include "config.php";

//Загружает гостевую книгу с диска. Возвращает содержимое книги. 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);

}

?>

428

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

Действительно, здесь нет ничего, кроме определений функций и… еще одной инструкции include (вздохните с облегчением — на этот раз последней). Она добавляет конфигурационные данные нашей книги — всего лишь одну-единственную константу GBook, определяющую имя файла, в котором гоствевая книга и будет храниться. "Для порядка" приведу и его (листинг 30.6).

Листинг 30.6. Конфигурация: config.php

<?

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

Что же у нас получилось в результате? Мы "растянули" простой сценарий на целых 5 файлов (если считать еще и .htaccess, то на 6). Что ж, если вы так думаете, я с вами соглашусь. Тут все дело в том, что для простых сценариев (а именно такой мы и рассматривали) трехуровневая схема построения оказы- вается чересчур уж "тяжеловесной". Про такую ситуацию в народе говорят: "из пушки по воробьям". Что же касается сложных систем, не следует забывать, что "единственность" ядра может сэкономить нам количество файлов, если у комплекса много различных интерфейсов (например, разветвленная система администрирования), не говоря уже о простоте отладки и поддержки. Кроме то- го, можно полностью разделить работу по написанию ядра и интерфейса меж- ду несколькими людьми.

Проверка корректности входных данных

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

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

На второй вопрос ответить довольно просто. Так как ядро не в состоянии "общаться" с шаблоном напрямую, а шаблон не может исполнять сложный код, остается единственный вариант — интерфейс. А что касается того, как выводить сообщение об ошибке, — вопрос довольно спорный. Мы рассмотрим лишь самое простое решение.

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

429

Интерфейс должен сгенерировать сообщение и передать его шаблону. Последний, "заметив" сообщение, может вывести текст контрастными буквами, например, вверху страницы. С этим никаких проблем быть не должно. Пусть интерфейс в случае ошибки создает переменную $Error и присваивает ей текст ошибки. Вот как может выглядеть шаблон:

. . .

<?if(Isset($Error)) {?>

<h3><font color=red>Произошла ошибка: <?=$Error?></font></h3> <?}?>

. . .

Такой подход, хоть и прост, оказывается немного неудобным для пользовате- ля. Действительно, ему сообщают, что произошла ошибка, но не говорят, на- пример, какое именно поле формы он заполнил неправильно. Пользователь желает, чтобы сообщения об ошибках появлялись напротив неверно введен- ных данных. К сожалению, без дополнительного программирования в шаблоне на PHP этого добиться довольно сложно (если вообще возможно). Единствен- ный имеющийся выход использовать шаблонизатор и написать для него фильтр (функцию, занимающуюся финальной обработкой блока страницы перед ее отправкой), которая будет в автоматическом режиме рядом со всеми тэгами формы проставлять возможные сообщения об ошибках (а заодно и ат- рибуты value в самих тэгах, чтобы поля формы сохраняли свои значения ме- жду вызовами сценария). Эта задача, пожалуй, потребует всей информации о PHP, заложенной в этой книге, и еще, вероятно, хорошего знания регулярных выражений Perl. Код, полностью решающий проблему, слишком объемен, что- бы уместиться на страницах данной книги.

Шаблонизатор

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

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

430

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

новку проблемы редизайна. Даже используя инструкцию include, мы попадем в тупик, если захотим изменить положения некоторых блоков на странице.

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

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

Сама идея шаблонизатора не является новой в Web-программировании. Скорее даже наоборот: существуют десятки систем, построенных по описанным ниже принципам. Большинство из них — коммерческие и часто довольно сложны. В то же время многие свободно распространяемые системы (во всяком случае, те, с которыми я знаком, — например, Mason, лебедевский Parser и др.) отличаются одним недостатком: синтаксис их языка излишне сложен, а потому отпугивает. Кроме того, часто для освоения этих шаблонизаторов требуются навыки не только дизайнера или HTMLверстальщика, но и программиста. Мы же, напомню в очередной раз, стремимся к тому, чтобы распределить разработку сценария по возможно большему числу независимых людей, многие из которых не знакомы с программированием.

Высказанные только что суждения являются моей личной позицией в вопросе шаблонизаторов, а потому, как и все субъективное, они вполне могут несколь- ко отличаться от действительности. Читателю предлагается самому их прове- рить после того, как он ознакомится с моделью шаблонизатора, предлагаемого в этой главе. Нужно заметить, что, конечно, каждая Web-студия считает свою собственную версию шаблонизатора самой лучшей в мире.

Традиционное построение страниц

Итак, сосредоточим все свое внимание на том, как желательно строить сценарии, чтобы максимально упростить проблему редизайна, а вместе с ней — добавление новых страниц в карту сервера. Многие программисты ограничиваются тем, что разбивают свои страницы на 3 логических блока: верхнюю часть (header), центральную часть (text) и нижний участок страницы (footer). Каждая из этих составляющих хранится в отдельном файле. Центральный блок (text) является главным: до начала работы он загружает из файла общую для всех страниц верхнюю часть, а в конце выводит