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

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

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

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

438

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

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

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

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

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

Автоматическая генерация названий

Если пользователь находится на сайте "Книжный магазин" в разделе "Философия" на заинтересовавшей его странице "Современность", то, конечно, в заголовке окна браузера ему бы хотелось видеть что-то вроде "Книжный магазин | Философия | Современность", а не просто "Современность". Мы уже договорились хранить название страницы в блоке Title. Но, конечно, мы бы не хотели записывать в каждой странице название полностью, потому что:

rв будущем мы можем перенести страницу в другой раздел;

rмы, возможно, захотим сменить разделитель | на /;

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

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

439

Специально для решения такого рода задач в нашем шаблонизаторе предусмотрим механизм, который я далее буду называть автоматической склейкой тел блоков. Вот как он работает. Если при обработке очередного блока шаблонизатор видит, что его тело начинается с подстроки [Клей], он определяет, что текст должен быть "пристыкован" к предыдущему телу одноименного блока, но не должен заменить его. В качестве "строки-клея" выступает значение блока с именем Клей. Это позволяет нам в будущем изменить символ "склейки" лишь в одном месте, чтобы это затронуло весь сайт. В случае, если указана пустая пара квадратных скобок [] (то есть имя блока было опущено), используется тело блока с именем DefaultGlue (см. листинг 30.12), а если и он отсутствует — то |.

Теперь при загрузке страницы /phil/index.html из листинга 30.11, пользователь увидит ее полное название, составленное из блоков Title всех "надкаталогов" и самого файла страницы. Мы добиваемся этого, определив блок Title следующим образом:

<?Block("Title","[]Философия")?>

Поддержка механизма поиска включаемых файлов

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

С помощью данной функции мы можем разбивать сложные шаблоны на части. Например, так можно было бы поступить с блоком, занимающимся формированием карты текущего раздела, особенно если существует несколько шаблонов, отображающих эту карту. Функцию Load() можно вызывать в любом месте страницы или даже из файла .htaccess. Блоки, генерируемые ей, будут вставлены непосредственно перед тем блоком, в котором она была вызвана.

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

440

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

Фильтры блоков

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

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

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

Поддержка трехуровневой схемы разработки сценариев

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

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

С загрузкой интерфейсов посредством Uses() все обстоит несколько сложнее. Вполне может возникнуть ситуация, когда один и тот же интерфейс требуется в разных местах шаблона страницы для выполнения различных действий. Функ-