Сайт Романа ПарпалакаЗаметкиТехнологииВеб-разработкаСистема управления шаблонами

Система управления шаблонами

5 июля 2006 года

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

Описание проблемы

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

Рассмотрим пример не самого маленького сайта (на 100 страниц), которым занимается начинающий дизайнер. Он берет FrontPage, создает прототип будущей страницы, содержащий, например, шапку со ссылками, пустую область контента (основного содержимого), боковую панель со служебной информацией и нижнюю область с копирайтом (куда ж без него). Для каждой добавляемой страницы в такой шаблон вставляется контент и сохраняется под новым именем.

К «плюсам» этого подхода относится возможность автоматической генерации навигационных ссылок, например, на дочерние страницы, по заранее составленной схеме сайта. Во FrontPage есть удобный редактор схем, пользоваться которым — одно удовольствие. На этом плюсы и заканчиваются.

Принципиальные недостатки таковы:

Последний недостаток становится особенно явным, когда есть несколько разделов с частично отличающимся оформлением страниц.

Применение более совершенного редактора, например, 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, чтобы он не обрабатывался нашим главным скриптом.

Какие достоинства у построенной нами системы?

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

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 Кб)

Литература

  1. Смирнов Д. В., Шаблоны в PHP для чайников.
  2. Котеров Д. В., Самоучитель PHP 4, BHV-Санкт-Петербург, Гл. 30. Код и шаблон страницы.
Поделиться

Комментарии

#1. 8 мая 2007 года, 21:06. C пишет:
А мне вот интересно, в вашем сайте, естественно разделение url виртуальное и все статьи хранятся не как в статье в каждом файле, а в базе :)
А файл template.zip тогда где на самом деле? Неужели всё же в этой же папке?
#2. 8 мая 2007 года, 22:20. Роман Парпалак пишет:
Сейчас сайт работает примерно так, как описано здесь. И работает он так уже больше года.

Движок блога с использованием БД я переписал месяца два назад. Летом дойдет очередь и до сайта.
#3. 24 мая 2007 года, 00:01. Андрей Иванов пишет:
Где то это я уже читал
#4. 24 мая 2007 года, 00:03. Андрей Иванов пишет:
Прошу прощения: ссылку внизу не увидел
#5. 24 мая 2007 года, 09:15. Роман Парпалак пишет:
Шаблонизатор, описанный у Котерова, сложнее (он бы выложил исходники где-нибудь в Сети. В свое время я их не нашел :) ). Оттуда я позаимствовал совсем немного. В тексте указано, что именно.
#6. 28 апреля 2008 года, 22:28. Vlad пишет:
Привел Ваш шаблонизатор до понятного (тем кто не очень в php) состояния.
На http://webrealjob.org/Files/Temp/site.rar костяк сайта. На его базе можно сразу влоб начинать строить
приличный сайт на php. Достоинство шаблонизатора — одинаковая работа на php4 и php5.
Был-бы признателен за скрипт добавления сообщений на страницы на базе данного шаблонизатора.

Спасибо. Vlad.
#7. 29 апреля 2008 года, 00:02. пишет:
Не понятно, о каких сообщениях идет речь — о самом содержимом страниц, или о комментариях. В любом случае их нужно сохранять либо в файлы, либо в базу данных. А это — элементарные задачи для программиста на PHP.

Я бы сказал, что Вы привели шаблонизатор до понятного состояния для тех, кто не очень в HTML. Основная задача данной статьи — показать, каким образом на PHP пишется шаблонизатор. Поэтому HTML и CSS были урезаны до минимума, как не относящиеся к теме.
#8. 6 мая 2008 года, 19:30. пишет:
...Вариантов написания шаблонизатора много. Простейший из них описан в заметке Шаблоны в PHP для чайников [1], с которой я рекомендую ознакомиться...
прикол) я его прочитал и написал http://maxreplace.zx6.ru

Оставьте свой комментарий

Ваше имя:

Комментарий:

Для выделения используйте следующий код: [i]курсив[/i], [b]жирный[/b].
Цитату оформляйте так: [q = имя автора]цитата[/q] или [q]еще цитата[/q].
Ссылку начните с http://. Других команд или HTML-тегов здесь нет.

Сколько будет 26+5?