Самоучитель PHP 4 - Котеров Д. В.
.pdf
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) является главным: до начала работы он загружает из файла общую для всех страниц верхнюю часть, а в конце выводит
Глава 30. Код и шаблон страницы |
431 |
нижнюю. Вот как примерно выглядит шаблон страницы при такой структуре сценария (листинг 30.7):
Листинг 30.7. Традиционное построение шаблона
<?include "Interface.php"?>
<?include "$DOCUMENT_ROOT/templ/header.htm"?>
Здесь идет главный текст страницы, возможно, включающий данные, сгенерированные интерфейсом Interface.php
<?include "$DOCUMENT_ROOT/templ/footer.htm"?>
Предполагается, что файлы header.htm и footer.htm хранятся в подкаталоге /templ корневого каталога сервера и содержат участки страниц, которые должны быть выведены до и после основного текста страницы. Если сайт небольшой и в нем используется не так уж много различных шаблонов страниц, данное решение является самым простым. В таких ситуациях его применение оправдано. Но нас интересует оформление больших и сложных сайтов. Предположим, наш ресурс содержит сотни страниц, построенных по описанной схеме. Давайте взглянем на проблему с этой позиции.
Сложность перестановки блоков
Первый недостаток увидеть легко: мы не можем ни добавить новый блок в страницу, ни изменить положения уже имеющихся. Если мы попытаемся это сделать, потребуется менять код сотен страниц сайта.
Необходимо заметить, что в нашем примере вряд ли придется когда-нибудь изменять порядок следования блоков, раз мы договорились рассматривать проблему с общих позиций, а не с частных.
"Расщепление" шаблона
Второй недостаток более очевиден для дизайнера: файлы header.htm и footer.htm, хотя и представляют собой логически один шаблон, все же разделены. Все мы привыкли к тому, что многие тэги HTML (такие как <body>, <table> и т. д.) имеют парные закрывающие, причем расположенные в том же самом файле. Но в ситуации с разделением шаблона на footer и header мы, наоборот, должны хранить большинство открывающих тэгов в одном файле, а закрывающие — в другом. В листинге 30.8 приведен пример верхней части шаблона страницы.
432 |
Часть V. Приемы программирования на PHP |
Листинг 30.8. Файл header.htm
<html>
<body bgcolor=white> <h1>Добрый день.</h1> <table><tr>
<td width=20%>Карта раздела: . . .</td> <td>
Видите, файл оборвался на открывающем тэге. Теперь взглянем на листинг 30.9:
Листинг 30.9. Файл footer.htm
</td>
</tr></table>
</body>
</html>
Он состоит исключительно из одних закрывающих тэгов. Потенциально, добавив в header.htm новый открывающий тэг, мы можем забыть закрыть его в footer.htm. Кроме того, такая конструкция несколько противоречит логике: две похожих по смыслу части шаблона содержатся в разных файлах. Мы уже обсуждали это выше и пришли к выводу, что данное построение оказывается довольно неудобным.
Сложность смены шаблона у части страниц
Еще один недостаток описанной схемы следует из предыдущего. Каждая страница должна "знать", где расположены файлы header.htm и footer.htm. Пусть у нас на сайте есть каталог, в котором "лежат" сотни файлов. Во время разработки оказалось, что шаблон для всех файлов в этом каталоге должен отличаться от шаблона всех остальных страниц (которых также немало). Значит, требуется создать еще одну пару header- и footer-файлов, назвав их, например, header1.htm и footer1.htm. Это в общем-то не представляет особой проблемы, сложность в другом: придется заменять ссылки во всех файлах каталога. Можно, конечно, сделать это посредством глобальных поиска и замены при помощи какого-нибудь текстового редактора (например, HomeSite фирмы Allaire), но, согласитесь, это решение выглядит как явно "лобовое". Кроме того, если мы имеем доступ к сайту только с использованием FTP, нам придется "скачивать" все страницы, редактировать их, а затем опять копировать на сервер. Естественно, для крупных информационных сайтов такие "накладные расходы" просто неприемлемы.
Возможно единственное решение этой проблемы — заставить страницы "наследовать" ссылку на шаблон каталога (или его родительского каталога), в котором они
Глава 30. Код и шаблон страницы |
433 |
находятся. Таким образом, поправив эту ссылку в информации о каталоге, мы автоматически изменим шаблон и у всех страниц в нем.
Для сравнительно небольших систем все же существует путь, обходящий, хоть и не очень удачно, рассматриваемую проблему. А именно, можно для каждого раздела сайта использовать отдельную пару header- и footer-файлов. В дейст-
вительности же эти файлы будут представлять собой лишь символические ссылки на "настоящие" шаблоны. Правда, эта схема работает лишь в системах Unix. Кроме того, она нисколько не упрощает ситуацию, когда разработчики решили перенести часть страниц из одного раздела в другой (сменив при этом их шаблоны).
Что такое шаблонизатор?
Итак, мы вновь столкнулись с множеством трудноразрешимых накладок (возможно, выглядящих для многих с первого взгляда как надуманные). Когда же они закончатся, спросите вы? Отвечаю: прямо сейчас.
Давайте взглянем "в корень" описанных выше сложностей. Почему они вообще возникают в этой задаче? Нетрудно догадаться: опять же из-за излишних зависимостей данных. Помните, эти зависимости привели нас в свое время к необходимости перехода от двухуровневой схемы построения сценариев к трехуровневой? Теперь они подводят нас к идее шаблонизатора.
Вспомним, что мы сделали тогда, чтобы убрать зависимости. Мы поменяли местами "поставщика" и "исполнителя". Идея выполнить обратную перестановку кажется абсурдной, т. к. мы опять придем к тому, с чего начали. Конечно, мы не будем так делать. Зададимся отвлеченным вопросом: что предпринимает общество, когда перед ним возникает чересчур большое количество нерешенных и необъяснимых задач? Оно придумывает себе богов. В программировании — то же самое. Раз мы не можем больше сослаться ни на генератор данных, ни на шаблон, значит, настало время реализовать нечто третье, "бога", управляющего всей системой в совокупности и распределяющего обязанности. Вы, наверное, догадались, что я снова имею в виду шаблонизатор.
Итак, шаблонизатор — это программный код, держащий "под контролем" все файлы на нашем сайте. Ни одно обращение к странице, ни один запуск сценария не может пройти без его непосредственного участия. В то же время шаблонизатор "маскирует" себя, создавая у пользователя впечатление, будто бы его и нет. Этим он похож на генератор данных в трехуровневой модели построения сценариев.
434 |
Часть V. Приемы программирования на PHP |
Теперь вы почувствовали, почему я применил здесь аналогию с богом? Ведь бог как раз удовлетворяет тем описаниям, которые даны в предыдущем абза- це!
Впрочем, идеология "вездесущего" кода не является для нас новой: нечто похожее мы уже рассматривали в главе 29, правда, с целью гарантированного подключения библиотекаря ко всем сценариям сайта. В рамках реализуемой "религии" мы применим точно такой же подход, только вместо библиотекаря будет подключаться и запускаться шаблонизатор.
Описание шаблонизатора
Сформулируем, что должен уметь делать наш будущий шаблонизатор. Конечно, все, что мы реализуем, будет лишь самым основным, что мы хотели бы получить от этой системы. В то же время в описанную концепцию чрезвычайно легко добавлять новые возможности (так уж она разрабатывалась). Для этого практически не придется переписывать имеющийся код, останется лишь вставить то, что нам нужно.
Вставка страниц в единый шаблон
Раньше главный текстовый блок страницы (text) запрашивал подключения к себе двух частей шаблона — footer и header. Но, раз мы в очередной раз поменяли местами "поставщика" данных и "исполнителя", посмотрим, нельзя ли пойти дальше. Давайте поиграем в такую словесную игру: "обработаем" первое предложение этого абзаца, переставив в нем понятия, соответствующие "исполнителю" и "поставщику". Получим буквально: шаблон запрашивает подключение к себе главного текстового блока страницы. Эврика, это и есть главная задача шаблонизатора!
Не хотите ли взглянуть с этой новой позиции на шаблон страницы? Тогда изучите листинг 30.10.
Листинг 30.10. Свежий взгляд на шаблон страницы: /templ/main.tmpl
<?Block("Output"?>
<html><head><title><?=Blk("Title"title></head> <body bgcolor=white>
<h1>Добрый день.</h1> <table><tr>
<td width=20%>Карта раздела: . . .</td> <td width=80%><?=Blk("Text")?></td> </tr></table>
</body></html>
Глава 30. Код и шаблон страницы |
435 |
Не обращайте пока внимания на команду <?Block("Output"?>. Ее смысл поясняется немного ниже.
Мы видим, что ненужное и опасное "расщепление" шаблона на два файла ушло в прошлое, а мы опять вернулись к простой модели. Будем хранить этот шаблон в фай-
ле /templ/main.tmpl.
Но позвольте, откуда же возьмется блок с именем Text, который выводится в середине этого шаблона? Вот задачу по его получению и возьмет на себя шаблонизатор. Предположим, пользователь обратился по адресу /news/weekly/today.html. Шаблонизатор, как я уже упоминал, "перехватит" это обращение и "возьмет" текстовый блок из файла today.html, расположенного в каталоге /news/weekly. Затем он передаст управление шаблону, который вставит этот текст в нужное место страницы и отправит последнюю браузеру.
Множественность блоков
Шаблонизатор, который вставляет все содержимое запрошенного файла в фиксированный шаблон, совершенно бесполезен в реальной практике. Это означает, что мы должны "научить" его:
rопределять имя шаблона индивидуально для каждой страницы;
rпозволять хранить в документе несколько блоков информации, а не только главный текст файла.
Последняя задача более важна, так что начнем с нее. Мы привыкли к тому, что любая страница сценария выполняется последовательно и представляет собой единый HTML-документ. Теперь нам придется отказаться от этого стереотипа (в общем-то, ведущего в тупик). Итак, любая страница сайта — это всего лишь набор блоков, которые будут вставлены в шаблон.
Блок — участок текста, имеющий имя (не обязательно уникальное), посредством которого можно ссылаться на этот текст. Мы уже видели, как это происходит в простейшем случае (см. листинг 30.10). Функция шаблонизатора Blk() возвращает текст (или содержимое, или тело) блока, имя которого указанно в ее параметрах. Содержимое блока может быть задано многократно, при этом последующее определение "затирает" текст предыдущего. Чуть ниже мы увидим, насколько данное качество оказывается полезным на практике.
Как же определять новые блоки в файле страницы? Для этого существует конструкция <?Block("имя")?>. Пример ее использования приведен в листинге 30.11.
Листинг 30.11. Файл данных страницы: /phil/index.html
436 |
Часть V. Приемы программирования на PHP |
<?Block("Title","[]Философия")?> <?Block("Text")?>
Конфликт индуцирует смысл жизни. Объект деятельности, пренебрегая деталями, методологически рефлектирует себя через постсовременный класс эквивалентности, открывая новые горизонты. Закон внешнего мира может быть получен из опыта.
<?Block("Cite")?>
Философия, конечно, порождена временем. Информация, как следует из вышесказанного, непредвзято подчеркивает принцип восприятия, отрицая очевидное.
Из листинга 30.11 следует, что мы можем задавать содержимое блока двумя разными способами. Самый простой — указать текст непосредственно вторым параметром функции Block(), как это сделано для блока Title. Второй способ незаменим для блоков, тела которых состоят из большого количества строк. А именно, мы можем опустить второй параметр функции Block(), в этом случае весь текст, который расположен до начала следующего блока либо до конца файла, будет восприниматься как тело. Я буду называть такие блоки многострочными. Особенностью многострочных блоков в том шаблонизаторе, который мы с вами сейчас напишем, является то, что из их содержимого удаляются начальные и концевые пробельные символы, в том числе символы перевода строки. В результате та пустая строка, которая присутствует в листинге, не попадет в шаблон — она будет удалена.
Текст, не принадлежащий ни одному из блоков, игнорируется. Например, мы могли бы написать какие-нибудь комментарии сразу после первой строки лис- тинга 30.11, и они были бы пропущены.
Наверное, вы уже догадались, как мы будем задавать имя шаблона для той или иной страницы. Название шаблона — не что иное, как содержимое блока Template, который воспринимается шаблонизатором как специальный. Но, конечно, мы не собираемся определять этот блок в каждой странице — иначе чем этот способ лучше использования участков header и footer? Посмотрим, что предлагает нам шаблонизатор.
Наследование блоков
Наверное, вы думаете, что страница /phil/index.html, которая генерируется листингом 30.11, состоит только из трех блоков — Title, Text и Cite. Это не так. Страница, без сомнения, включает перечисленные блоки, но она также состоит и из всех блоков, которые заданы для каталогов /phil и /. Каталоги ведь ничем не хуже файлов. Соответственно, каждый каталог также может иметь собственный набор блоков, которые будут унаследованы всеми файлами в нем, а также файлами его подка-
талогов.
Глава 30. Код и шаблон страницы |
437 |
Предположим, что для каталога /phil определяется блок Title, содержащий, скажем, строку Weekly. В то же время файл index.html также определяет блок Title. Что произойдет в этом случае? А произойдет следующее: в шаблоне будет доступно только тело последнего блока. Иными словами, тот блок, который определяется в файле, заменит собой свое старое значение из каталога.
Нетрудно теперь догадаться, как происходит процесс сбора блоков для конкретной запрошенной страницы. Вначале шаблонизатор получает все блоки корневого каталога, затем обрабатывает блоки подкаталога, причем уже имеющиеся одноименные блоки перезаписываются. Описанный процесс продолжается до тех пор, пока не будет достигнут файл, который запрошен пользователем. Такая организация шаблонизатора позволяет нам задавать для всех блоков значения по умолчанию. Эти значения будут использованы шаблоном в случае, если те или иные блоки не "переопределяются" в файле страницы. Чтобы задать тело по умолчанию для блока, достаточно добавить его к блокам корневого каталога сайта.
Мы знаем, что блоки файла хранятся в самом этом файле. Где же находятся блоки каталога? Конечно, в специальном файле, расположенном в этом каталоге.
Хранить блоки каталогов в отдельном централизованном хранилище (наподо- бие Реестра Windows) было бы большим просчетом. Этим мы перечеркнули бы принцип минимизации зависимостей данных, о котором так много сказано в этой главе.
Я предлагаю использовать в качестве такого файла .htaccess. Чтобы Apache не "ругался" на не непонятные ему директивы <?Block(...)?>, мы снабдим их символами комментария # в начале строки. Конечно, таким способом мы не сможем задавать многострочные блоки для каталогов, но, как показывает практика, в большинстве случаев это и не нужно. В листинге 30.12 показан пример файла .htaccess, расположенного в корневом каталоге сервера и задающего значения блоков по умолчанию.
Листинг 30.12. Блоки для корневого каталога: /.htaccess
#<?Inc("templ")?> #<?Block("DefaultGlue"," | ")?> #<?Block("Template","default.tmpl")?> #<?Block("Title","Тестовый сервер")?>
#Связываем имя обработчика с конкретным файлом. Action templhandler "/php/TemplateHandler.php?"
#Документы этого типа мы желаем "пропускать" через наш обработчик. AddHandler templhandler .html .htm
