Шаблонизация в веб-программировании. Создание шаблонизатора на PHP

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

Первые работы начинающего веб-программиста выглядят, как правило, примерно так:

echo '<div class="post_entry">';
echo '<h2>' . $article['title'] . '</h2>';
echo '<div class="date">' . $article['date'] . ' &bull; Категория: <a href="/' . $article['category_alias'] . '/' . $article['article_alias'] . '">' . $article['category'] . '</a></div>';
echo $article['text'];
echo '</div>';

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

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

Шаблон для примера выше может выглядеть следующим образом:

<div class="post_entry">
  <h2>{TITLE}</h2>
  <div class="date">{CREATED} &bull; Категория: <a href="{CATEGORY_LINK}">{CATEGORY}</a></div>
{CONTENT}
</div>

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

"Натянуть" данные на шаблон очень просто, достаточно воспользоваться функцией str_replace или preg_replace для замены псевдопеременных в шаблоне. Но грамотнее для этой цели написать собственный простой шаблонизатор.

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

<?php
 
  class QTemplate
  {
      private $content = NULL;
      private $res_content = NULL;
 
      function QTemplate($tpl_dir, $tpl_name)
      {
          if ( ! file_exists($tpl_dir . $tpl_name . '.tpl') ) return;
          $this->content = file_get_contents($tpl_dir . $tpl_name . '.tpl');
      }
 
      function assign_vars($vars)
      {
          $this->res_content = $this->content;
 
          foreach( $vars as $blockname => $value )
          {
              $this->res_content = preg_replace('/{' . $blockname . '}/i', $value, $this->res_content);
          }
      }
 
      function render()
      {
          if ( $this->res_content == '' ) $this->res_content = $this->content;
 
          return $this->res_content;
      }
  }
 
?>

Шаблонизатор представляет из себя PHP-класс QTemplate. При создании экземпляра класса предается путь к папке с шаблонами и имя шаблона. Метод assign_vars "натягивает" данные на шаблон, то есть заменяет псевдопеременные в шаблоне на соответствующие им данные. Соответствия передаются в аргументе vars, который должен представлять из себя массив со следующей структурой:

array(
    'ИМЯ_ПСЕВДОПЕРЕМЕННОЙ' => 'ДАННЫЕ',
    'ИМЯ_ПСЕВДОПЕРЕМЕННОЙ' => 'ДАННЫЕ',
    'ИМЯ_ПСЕВДОПЕРЕМЕННОЙ' => 'ДАННЫЕ'
    );

При замене псевдопеременных результат сохраняется во внутреннюю переменную res_content, при этом переменная content содержит исходный текст шаблона. Это позволяет много раз использовать один экземпляр шаблонизатора для генерирования HTML-кода.

Метод render возвращает готовый HTML-код. Вызывается, соответственно, после замены псевдопеременных методом assign_vars.

А теперь давайте рассмотрим пример использования шаблонизатора:
<?php
 
  $tpl_dir = 'templates/default/'; //--Путь к папке с шаблонами
 
  require 'qtemplate.php'; //--Подключаем шаблонизатор
 
  //--Тут должен находиться код получения статей из базы данных в массив $articles
 
  $main_tpl = new QTemplate($tpl_dir, 'main'); //--Загружаем шаблон main
  $article_tpl = new QTemplate($tpl_dir, 'article'); //--Загружаем шаблон article
 
  $content = '';
 
  //--"Пробегаемся" по всем статьям
  foreach ( $articles as $article )
  {
      //--"Натягиваем" данные на шаблон
      $article_tpl->assign_vars( array(
          'TITLE' =>         $article['title'],
          'CREATED' =>       $article['date'],
          'CATEGORY_LINK' => '/' . $article['category_alias'] . '/' . $article['article_alias'],
          'CATEGORY' =>      $article['category'],
          'CONTENT' =>       $article['text']
          ) );
      //--Генерируем HTML-код статьи
      $content .= $article_tpl->render();
  }
 
  //--"Натягиваем" контент на главный шаблон
  $main_tpl->assign_vars( array(
      'TITLE' =>   'Новости',  
      'CONTENT' => $content
      ) );
  //--Генерируем HTML-код всей страницы и выводим его
  echo $main_tpl->render();
 
?>

PHP-скрипт загружает два шаблона: main, содержащий структуру HTML-страницы (DOCTYPE, метатеги и т. д.) и article, содержащий структуру статьи. Код прокомментирован и, я надеюсь, предельно понятен. В цикле данные каждой статьи "натягиваются" на шаблон article, далее готовый HTML-код накопительно добавляется в переменную content. Полученный контент помещается в шаблон main. Это очень важная возможность — возможность заменять псевдопеременную одного шаблона результатом обработки другого шаблона (или нескольких шаблонов). В результате генерируется страница-категория со списком статей. Для более детального рассмотрения предлагаю скачать архив проекта, где вы найдете код шаблонизатора, тестовой страницы, а также необходимые шаблоны.

Преимущества, дающие использование шаблонов при генерировании кода HTML-страниц:

  • Во-первых, чистота кода. Программный код не содержит HTML-кода, поэтому легко читается. В то же время, шаблон содержит только HTML-код, в котором видно где и какие данные вставляются (для этого давайте осмысленные имена псевдопеременным).
  • Во-вторых, разделение программного и HTML-кода дает возможность независимой разработки движка и дизайна. Чтобы что-то изменить во внешнем виде готового сайта достаточно внести изменения в соответствующий шаблон, при этом не нужно владеть навыками программирования.
  • Сайт может иметь несколько дизайнов. Чтобы полностью изменить дизайн сайта достаточно просто изменить путь к папке с шаблонами.

Плюсов, конечно, намного больше, это лишь наиболее выраженные. В общем, я советую использовать шаблоны всегда и везде. Удачного кодинга!

Комментарии

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

 
Joker-jar
10 декабря 2011, 17:50
#1
 

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

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

Михаил
1 января 2012, 14:27
#2
 

Как и обещали.

Конечно статьи по этой теме очень нужны.

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

Drakmail
16 января 2012, 19:19
#3
 

А шаблонизатор этого сайта поддерживает что-нибудь из этого: циклы, вложенные циклы, фильтры, собственные теги? Если да, то было бы интересно почитать, как реализовали.

Joker-jar
17 января 2012, 0:31
#4
 

Александр, сам шаблонизатор достаточно простой, циклы и прочее, при необходимости, реализуется на уровне PHP. Пример: вывод комментариев. В шаблоне статьи присутствует переменная {COMMENTS}. Выполняется SQL-запрос для получения списка комментариев и подгружается шаблон комментария. Далее создается массив для наполнения комментариями:

$commentsarr = array();

По циклу каждый комментарий оборачивается в шаблон и добавляется в этот массив:

foreach ($ret as $comment) //--$ret - массив комментариев, полученный из MySQL
{
    $i++;
 
    $comment_text = $this->doc->modules['lib']->filter_comment($comment['comment'], $comment['gid']);
 
    $item_tpl->assign_vars(array(
        'ID' =>            $comment['id'],
        'NUMBER' =>        ($page - 1) * $items_per_page + $i,
        'AUTHOR' =>        $comment['author'],
        'CREATED' =>       $this->doc->modules['lib']->rusdate($comment['ucreated']),
        'IP' =>            $comment['ip'],
        'COMMENT' =>       $comment_text
        ));
    $commentsarr[] = $item_tpl->render();
}

В конечном счете эти комментарии попадают в шаблон статьи:

'COMMENTS' =>   implode(NEWLINE, $commentsarr)

Ну вот как-то так.

Drakmail
23 января 2012, 11:34
#5
 

Ну это-то ясно, мне просто понравилось, как это в h2o сделано (ну и в django) - это удобные теги для циклов и условных операторов:

{% for item in items %} например, или {% if x %}. На мой взгляд, гораздо удобнее, чем <?php foreach $item in $items ?> и т.д.

Хотя с помощью простого preg_replace, наверное, такие операторы тоже реализовать можно.

Joker-jar
23 января 2012, 12:37
#6
 

Я ими никогда не пользовался и представления имею мало. Как выглядит непосредственно тело цикла в таком шаблоне?

Drakmail
23 января 2012, 23:01
#7
 

Примерно так:

в контроллере передаем во вьюху массив вида

$items = array ('key1'=>'value1','key2'=>'value2');

Далее, во вьюхе, с применением h2o, можно вывести этот массив в красивом виде примерно так:

{% for key,value in items %}
<div class="item">
    <p class="header">{{ key }}</p>
    <p class="body">{{ value }}</p>
</div>
{% endfor %}

Вот как-то так. Мне почему-то такой синтаксис приятнее, чем с прямой вставкой php кода.

Joker-jar
24 января 2012, 1:31
#8
 

Метод интересный и наверняка удобный. Есть, правда, один нюанс. Шаблоны придумали и в том числе для того, чтобы внешним видом сайта мог заниматься дизайнер/верстальщик, далекий от программирования, но знающий язык разметки. А в этом случае шаблоны уже имеют некие прототипы программного кода.

Допустим, необходимо в цикле чередовать класс какого-нибудь элемента (чтобы, скажем, каждый четный элемент отличался по цвету). Тогда в шаблоне придется делать и условия.

Drakmail
24 января 2012, 10:42
#9
 

Кстати, и это там тоже продумано ) Вот дополнение к предыдущему примеру:

{% for key,value in items %}
<div class="item" style="background-color: {% cycle "#fffff", "#00000" %}">
    <p class="header">{{ key }}</p>
    <p class="body">{{ value }}</p>
</div>
{% endfor %}

Здесь с помощью тега cycle происходит чередование цвета фона при проходе по циклу. Также, можно делать свои теги и фильтры, для сокращения количества логики в шаблоне.

Вообще, с переходом на h2o я всё чаще стал делать всю логику в модели/контроллере, а шаблоны использую просто для циклов и вывода html.

Светозар
3 февраля 2012, 1:04
#10
 

Хотелось бы увидеть в полностью шаблонизатор, и процесс вывода полной и краткой новости...

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

Ваше имя
 
Ваш e-mail
 
Комментарий