Ещё раз о шаблонах

в 8:38, , рубрики: php, шаблонизатор, метки: ,

Рано или поздно девелоперу, создающему сайты статусом выше «сайт-визитка», приходится сталкиваться с таким понятием как «шаблоны» или «шаблонизация» визуального представления (не шаблоны проектирования). Что это такое? Механизм шаблонов позволяет отделять визуальное представление веб-приложения (по-скольку работаю только с веб-приложениями, то и рассуждать буду в этом контексте) от бизнес-логики таким образом, чтобы при изменении, например, внутренней логики попутно не приходилось переделывать всю html-верстку. На этом поприще уже давно существует несколько отдельно стоящих флагманских решений, позволяющих создавать довольно гибкие приложения в плане разделения труда дизайнеров-верстальщиков и программистов, а также предотвращать запутанность кода в больших приложениях. Описывать все их нет смысла. Это уже сделано до меня и не один раз. Помимо этого, почти каждая CMS и фрэймворк имеет собственные решения для отделения логики приложения от логики представления.

Конечно, когда речь идет о проекте в десятки тысяч строк кода, когда у заказчика есть возможность нанять и программиста, и верстальщика, то и функционал шаблонов будет только пользой для всех участников проекта. Но когда разработчик в простецкий сайт в 3 страницы начинает втуливать Smarty, это ИМХО уже перебор. В таких случаях я люблю вспоминать выражение Джорджа Шлоснейгла из его замечательной книги «Профессиональное программирование на PHP» – «попытка убить муху молтком».

Действительно, встраивая все тот же Smarty в весьма простой по функционалу сайт, разработчик попросту тратит время. А время, как известно, — деньги. Да и в большинстве случаев заказчику безразлично на каком шаблонизаторе будет работать его сайт. Заказчик больше печется о затраченном времени.

Что же делать, если сайт не «визитка», но и не второй Amazon? Лично я считаю, что в этом случае оптимальное решение проблемы — воспользоваться своей самописной системой шаблонов, весь функционал которой, заточен только для решения узкого круга задач, необходимых для текущего ресурса. Впоследствии вы, возможно, выведите свою «формулу» универсального шаблонизатора с неким минимальным набором функций, расширяемую по мере необходимости в отдельно взятом проекте.

Может показаться, что автор сей статьи весьма скептически относится к Smarty и другим шаблонизаторам. Это не так. Я довольно долго работал с проектом, в котором роль шаблонизатора выполнял все тот же Smarty. И хочу заметить, мне весьма понравилось использование этой системы шаблонов в контексте обширного по функционалу проекта.

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

  • Возможность назначать переменные шаблону
  • Отдельное пространство имен переменных для отдельно взятого html-шаблона
  • Возможность использования одного шаблона для общего каркаса страницы во избежание повторения конструкций вида require 'header.tpl';… require 'footer.tpl'; в каждом файле

К слову, «средний» проект — это некий эталон измерения сложности веб-приложения по моей личной шкале и соотвествует примерно следующим критериям:

  • Проект является интернет-магазином или социальным сообществом
  • 10-20 типов страниц. Подчеркиваю — НЕ страниц, а типов страниц. Например: страница описания товара. Т.е. html-шаблон для этого типа страниц будет один, но самих страниц, когда каталог заполнится контентом будет много

У каждого девелопера эта шкала своя, так что в этом вопросе прошу строго не судить.

Перейдем от слов к делу.

Рассмотрим простейший класс, который состоит всего из трех методов.

<?php

class Template {

    private $_tmpl;
    private $_vars = array();

    public function __construct($filename) {
        $this->_tmpl = $filename;
    }

    public function assign($name, $value) {
        $this->_vars[$name] = $value;
    }

    public function render() {
        if (count($this->_vars) > 0) {
            extract($this->_vars);
        }

        $tmplPath = "../templates/{$this->_tmpl}.tpl";

        if (file_exists($tmplPath)) {
            require $tmplPath;
        } else {
            throw new Exception("Шаблон <strong>{$this->_tmpl}</strong> не найден");
        }
    }

}

Давайте рассмотрим пример с применением этого класса. Создадим следующую структуру каталогов:

/wwwroot
|
— /classes
| — Template.php
— /templates
| — Main.tpl
| — Catalog.tpl
| — Product.tpl
| — Index.tpl
| — 404.tpl
|-- index.php

Main.tpl

<!-- Общий каркас страницы -->
<!DOCTYPE html>
<html>
    <head>
        <title></title>
    </head>
    <body>
        <?php echo $content ?>
    </body>
</html>

Catalog.tpl

<!-- Это шаблон страницы каталога -->
<p>Каталог №<?php echo $ID ?></p>

Product.tpl

<!-- Это шаблон страницы продукта -->
<p>Страница описания продукта №<?php echo $ID ?></p>

Index.tpl

<!-- Это главная страница -->
<p>Это главная страница сайта</p>

404.tpl

<!-- Это шаблон страницы 404-й ошибки -->
<p>Страница не найдена!</p>

index.php

/**
 * Буферизация вывода.
 * Все, что будет выводиться echo-м или print-ом, будет попадать в буфер, а не в браузер пользователю.
  */
ob_start();

// Подключаем шаблонизатор
require dirname(__FILE__).'/classes/Template.php';

// Производим проверку маршрутизации и включаем необходимые шаблоны
// Если не один из шаблонов не найден, отправляем клиенту код ошибки и показываем шаблон 404.tpl
// Если маршрут не задан, выводим главную страницу
if (isset($_GET['route']) && $_GET['route'] == 'catalog') {
    $tmpl = new Template('Catalog');
    $tmpl->assign('ID', $_GET['ID']);
    $tmpl->render();
} else if ($_GET['route']) && $_GET['route'] == 'product') {
    $tmpl = new Template('Catalog');
    $tmpl->assign('ID', $_GET['ID']);
    $tmpl->render();
} else if (!isset($_GET['route'])) {
    $tmpl = new Template('Index');
    $tmpl->render();
} else {
    header("HTTP/1.0 404 Not Found");
    $tmpl = new Template('404');
    $tmpl->render();
}

// Получаем содержимое буфера
$content = ob_get_clean();

// Инициализируем шаблон каркаса страницы и выводим ранее сгенерированное содержимое
$tmpl = new Template('Main');
$tmpl->assign('content', $content);
$tmpl->render();

Как видно из примера, содержимое страниц catalog и product изменяется в зависимости от значения $_GET['ID']. Т.о. мы получили два типа шаблонов и неограниченное количество страниц, генерируемых приложением из этих шаблонов.

Вывод: не стремитесь во всех проектах, которыми вы занимаетесь, использовать навороченные библиотеки шаблонизаторов, предоставляющих большое обилие всевозможных инструментов, в большинстве случаев не используемых в должном объеме. Лучше всего написать свое решение, которое поможет сэкономить время, системные ресурсы, а главное — нервы останутся в порядке.

Спасибо за внимание.

Автор: snager


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js