Система управления шаблонами
Эта статья написана для начинающих
Описание проблемы
Есть много способов делать сайты. Но существует ряд рекомендаций, следуя которым можно построить легко масштабируемый и гибкий сайт (до определенных пределов, разумеется).
Рассмотрим пример не самого маленького сайта (на 100 страниц), которым занимается начинающий дизайнер. Он берет FrontPage, создает прототип будущей страницы, содержащий, например, шапку со ссылками, пустую область контента (основного содержимого), боковую панель со служебной информацией и нижнюю область с копирайтом (куда ж без него). Для каждой добавляемой страницы в такой шаблон вставляется контент и сохраняется под новым именем.
К «плюсам» этого подхода относится возможность автоматической генерации навигационных ссылок, например, на дочерние страницы, по заранее составленной схеме сайта. Во FrontPage есть удобный редактор схем, пользоваться которым — одно удовольствие. На этом плюсы и заканчиваются.
Принципиальные недостатки таковы:
- Качество создаваемого кода. FrontPage обладает ограниченными возможностями, HTML-код станиц содержит слишком много мусора.
- Избыточные расходы. Изменение содержания одной страницы приводит к изменению соседних страниц, вследствие чего увеличивается количество файлов, передаваемых и хранимых на сервере.
- Изменение дизайна сайта весьма затруднительно. В принципе, поиск и замена по всем файлам с использованием регулярных выражений может помочь делу, но при
более-менее значительных изменениях они не помогут.
Последний недостаток становится особенно явным, когда есть несколько разделов с частично отличающимся оформлением страниц.
Применение более совершенного редактора, например, Dreamweaver, кардинально проблему не решает. Необходимо использование качественно иных технологий.
Идеи
С моей точки зрения очень важным и продуктивным оказывается принцип минимизации избыточности данных, описанный в [2]. Согласно этому принципу, любые данные, которые использует в своей работе сайт, должны храниться в единственном месте. Действительно, недостатки рассмотренного варианта возникают потому, что создается шаблон, для каждой создаваемой страницы в шаблон вставляется контент и сохраняется под новым именем. Таким образом, HTML-код шаблона оказывается растиражированным по всем файлам и тесно переплетенным с содержанием страниц.
Из этого принципа вытекает другой, широко используемый принцип разделения оформления и содержания. Я предлагаю использовать многоуровневое разделение: в одном месте (файлы или база данных) хранить контент, в другом — логическую структуру документа (код HTML), в третьем — визуальное оформление (стили CSS), в четвертом — программный код.
Я собираюсь реализовать эти идеи в простой системе шаблонизации.
— Для чего нужна собственная система, когда можно взять готовую, — спросите вы, — ведь вокруг столько свободно распространяемых систем. Отвечаю. PHP — настолько простой и мощный (огромным количеством встроенных функций) язык, что за промежуток времени, сравнимый со временем освоения сторонней системы, вполне реально написать более-менее стоящий скрипт. Собственное решение гораздо легче дорабатывать и расширять в случае необходимости. Почти наверняка оно будет работать намного быстрее сторонних шаблонизаторов. При грамотной реализации оно будет безопаснее, так как никто не будет знать исходный код.
Шаблонизатор
Вариантов написания шаблонизатора много. Простейший из них описан в заметке Шаблоны в PHP для чайников [1], с которой я рекомендую ознакомиться. Более продвинутая система приводится в [2]. За основу мы возьмем первую заметку и доведем шаблонизатор до рабочего состояния.
Из каждого файла мы убираем все оформление, оставляя только меняющуюся от страницы к странице информацию. Договоримся, что такие файлы будут иметь расширение htm (разумеется, можно выбрать любое расширение). Мы хотим, чтобы файл *.htm мог содержать несколько блоков, например, заголовок, основное содержимое, дату создания и т. д. Для разметки блоков удобно использовать конструкции РНР; достоинства этого подхода будут указаны ниже. Мы введем специальный набор переменных, каждой из которых будем присваивать значение соответствующего блока. В [2] разметка осуществляется вызовами функций. Последний вариант сложнее в реализации, но несколько удобнее на практике, так как, например, если в тексте встречается апостроф, то нам его придется экранировать символом обратной наклонной черты: \'. Как показывает практика, такой символ появляется очень редко. И даже если вы случайно сделаете эту ошибку, ее легко отловить. Создаем файл 1.htm в директории dir1:
<?
$page_title="Страница 1";
$page_text='
<p>Текст страницы 1</p>
';
?>
В шаблоне в необходимых местах нужно вывести значения переменных. Он может выглядеть, например, так:
<html>
<head>
<title><?=$page_title?> - Раздел 1 - Суперсайт</title>
</head>
<body>
<?=$page_text?>
</body>
</html>
Каждому типу страниц должен соответствовать свой шаблон. Связывать шаблоны и страницы, вставляя в последние команды типа include "template.php" – не самая лучшая идея. Во-первых, имена шаблонов будут прописаны в каждом файле, что приводит к избыточности информации. Во-вторых, если с текстом каждой страницы мы хотим проделать одни и те же действия, их придется прописывать в шаблоне. А если шаблонов много? Совершенно необходим «самый главный скрипт», который будет вызываться каждый раз при обращении к страницам, управлять шаблонами и производить некоторые дополнительные действия.
Выделяем для файлов шаблонизатора отдельную директорию, например, engine, помещаем в нее основной скрипт под именем main.php. В этой же директории создаем поддиректорию template для хранения шаблонов. В начале main.php необходимо проверить, не вызвал ли пользователь его напрямую из браузера. Вот вариант такой проверки, взятый из [2]:
$FileName = strtr(__FILE__, "\\", "/");
$ReqName = ereg_Replace("\\?.*","",
strtr($_SERVER['QUERY_STRING'],"\\","/"));
if (eregi(quotemeta($ReqName), $FileName)) {
echo "Попытка неавторизованного доступа!";
exit();
}
В переменной $ReqName хранится часть URL до знака вопроса, то есть это путь к файлу от корневой директории веб-сервера и его имя.
Теперь самое время подумать, какой шаблон будет использоваться для того или иного файла. Я предлагаю для этого использовать часть URL. Например, все страницы вида http://www.server.ru/dir1/* имеют шаблон dir1, http://www.server.ru/dir2/* — dir 2 и т.д.
$dir_array = explode("/", $ReqName);
$page_type = $dir_array[1];
if ($page_type == "index.htm")
$page_type = "home";
В переменной $page_type мы выделили первую директорию в URL (например, "dir1" или "dir2"). При обращении к главной странице в ней будет храниться значение "home". В [2] сделано по-другому, шаблон можно сопоставить либо отдельной странице, либо любой директории, не обязательно первого уровня. В последнем случае, действие шаблона распространяется и на все поддиректории, в которых шаблон не переопределен.
Запоминаем текущую директорию (она еще понадобится в дальнейшем) и переходим в директорию, в которой находится файл.
$php_dir = getcwd()."/";
chdir("../".dirname($ReqName));
Если запрашиваемого файла нет, выдаем код ответа 404, соответствующую страницу ошибки и завершаем работу.
if (!is_file(basename($ReqName))) {
header("HTTP/1.1 404 Not Found");
include $php_dir."error404.html";
exit();
}
В принципе, уже можно вызвать файл и шаблон:
require basename($ReqName);
require $php_dir."template/".$page_type.".php";
Чтобы скрипт main.php вызывался автоматически, в файле .htaccess корневой директории веб-сервера необходимо прописать следующие инструкции:
Action htm_handler "/engine/main.php?"
AddHandler htm_handler .htm
Однако, в зависимости от настроек сервера, main.php может вызываться или при наличии соответствующего *.htm файла (а при его отсутствии веб-сервер генерирует сообщение о 404 ошибке самостоятельно), или в любом случае. Для перестраховки и универсальности решения (администратор хостинга может отключить возможность изменения настроек) проверка существования файла включена в main.php. Укажем и в файле .htaccess страницу 404 ошибки (для всех файлов, не только *.htm):
ErrorDocument 404 /engine/error404.html
Для файла ошибки выбрано расширение html, чтобы он не обрабатывался нашим главным скриптом.
Какие достоинства у построенной нами системы?
- Применение конструкций PHP для разметки блоков избавляет нас от написания парсера — участка кода, обеспечивающего считывание и обработку. Подобный парсер, написанный на PHP и интерпретируемый им, будет в любом случае работать медленнее, чем встроенный скомпилированный парсер PHP-кода.
- Шаблонизатор содержит только необходимый нам код, ничего лишнего в нем нет.
- Наш шаблонизатор выводит страницы практически с максимально возможной для PHP скоростью.
- Функциональность шаблонизатора легко расширяется.
Для демонстрации последнего свойства рассмотрим возможность обработки текста страниц. Вызов шаблона необходимо окаймить функциями, буферирующими вывод:
ob_start();
require $php_dir."template/".$page_type.".php";
$out = ob_get_contents();
ob_end_clean();
После выполнения этих инструкций в переменной $out окажется все содержимое страницы, предназначенное для вывода в браузер. С ним можно проводить любые манипуляции. Например, заменим все встречающиеся теги <br> на <br />, как того требует стандарт XHTML:
$out = str_replace('<br>', '<br />', $out);
Теперь для экономии трафика и ускорения загрузки страниц применим gzip-сжатие. Операция совершенно безобидная; если браузер не понимает gzip-сжатие, то данные отправляются в исходном виде.
ob_start();
ob_start('ob_gzhandler');
echo $out;
ob_end_flush();
header('Content-Length: '.ob_get_length());
ob_end_flush();
Когда текст страницы формируется PHP, в ответе сервера нет таких HTTP-заголовков, как Content-Length, Last-Modified. Их, все-таки, желательно устанавливать.
Заголовок Last-Modified должен содержать дату и время последнего изменения страницы. Его, как и любой другой заголовок, необходимо отсылать до любой инструкции вывода (в нашем случае, до последней инструкции ob_end_flush), например, таким способом:
/* получаем время последнего изменения файла */
$mt = filemtime(basename($ReqName));
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $mt).'; GMT');
Я расскажу о том, какие еще возможности можно реализовать. Переменные файла *.htm легко прочитать инструкцией include. Их значения можно вывести в форму, а потом сохранить опять в файл операторами вида fwrite($f, "$page_text='".$page_text."';\n"). Несколько строчек PHP – и мы получили редактор страниц. Картину несколько портит возможная необходимость исполнения PHP-кода на некоторых страницах, например, в скрипте поиска. Один из подходов – создание отдельного шаблона для каждой такой страницы и хранение кода в шаблонах. Другой вариант – вынесение исполняемого кода в функцию с определенным именем, и вызов этой функции в нужном месте в main.php. Правда, в последнем случае редактор немного усложнится.
Я ничего не сказал об организации шаблонов — файлов в директории engine/template. К ним тоже можно применить наш руководящий принцип и отделить повторяющиеся фрагменты.
Представленный шаблонизатор почти по всем параметрам лучше варианта с использованием FrontPage, о котором говорилось в начале статьи. Правда, в нем нет возможности автоматической генерации навигационных ссылок. К сожалению, эта проблема не имеет простого решения. Нужно организовать грамотное хранение структуры сайта, обеспечивающее как возможность чтения при выводе страниц, так и удобное редактирование при добавлении страниц или изменении структуры. Если возможность редактирования структуры будет реализована на PHP и если к ней добавить редактор страниц, о котором шла речь немного выше, то мы получим практически полноценную CMS — систему управления сайтом.
Путь построения правильного сайта намечен. Дело за малым — реализовать этот путь :)
Чтобы помочь вам, я собрал в архив файлы, упомянутые здесь. Это рабочий пример описанного шаблонизатора, который можно взять за основу и изменить так, как вам нужно.
Скачать шаблонизатор (2 Кб)
Литература
- Смирнов Д. В., Шаблоны в PHP для чайников.
- Котеров Д. В., Самоучитель PHP 4, BHV-Санкт-Петербург, Гл. 30. Код и шаблон страницы.
Комментарии
А файл template.zip тогда где на самом деле? Неужели всё же в этой же папке?
Движок блога с использованием БД я переписал месяца два назад. Летом дойдет очередь и до сайта.
На
приличный сайт на php. Достоинство шаблонизатора — одинаковая работа на php4 и php5.
Был-бы признателен за скрипт добавления сообщений на страницы на базе данного шаблонизатора.
Спасибо. Vlad.
Я бы сказал, что Вы привели шаблонизатор до понятного состояния для тех, кто не очень в HTML. Основная задача данной статьи — показать, каким образом на PHP пишется шаблонизатор. Поэтому HTML и CSS были урезаны до минимума, как не относящиеся к теме.
Оставьте свой комментарий