Реализация шаблонов PHP с использованием tpl файлов

Раздел: Программирование -> Шаблонизация

В PHP шаблоны представляют собой текстовые файлы (обычно с расширением .tpl или .phtml), которые содержат HTML разметку и вкрапления PHP кода для вставки динамических данных. Цель шаблонизации - отделить логику приложения от представления. Рассмотрим несколько подходов.

Основные подходы к шаблонизации

Как организовать вывод HTML отдельно от логики с помощью include?

Самый простой способ - использовать в основном файле index.php директиву include для подключения шаблона. Переменные, объявленные в index.php, доступны в подключаемом файле.

// index.php
$title = 'Главная страница';
$content = 'Добро пожаловать!';
include 'template.tpl';

Header php footer php (шаблоны header и footer в php)

<!-- template.tpl -->
<!DOCTYPE html>
<html>
<head><title><?php echo $title; ?></title></head>
<body><h1><?= $content ?></h1></body>
</html>

Index php tpl (шаблоны (tpl) в php)

Проблемы: Все переменные из основного скрипта становятся доступными в шаблоне, что может привести к конфликтам имён. Для каждого нового набора данных требуется повторно объявлять переменные. Отсутствует контроль области видимости.

Решение: Использовать функцию с локальной областью видимости и передавать только нужные переменные.

Как создать простой и безопасный шаблонизатор на основе буферизации?

Эффективный способ - написать функцию render(), которая принимает путь к шаблону и ассоциативный массив переменных. Функция использует extract() для распаковки массива, затем включает шаблон через буферизацию вывода, возвращая результат как строку.

function render($template, $data = []) {
    extract($data);
    ob_start();
    include $template;
    return ob_get_clean();
}

// Использование
$data = ['title' => 'О нас', 'content' => 'Текст страницы'];
$html = render('about.tpl', $data);
echo $html;

Index php option layout (настройка макета (layout) в php)

<!-- about.tpl -->
<h2><?= $title ?></h2>
<p><?= $content ?></p>

Проблема: Прямое использование extract() с внешними данными может привести к перезаписи существующих переменных (например, $this в ООП). Также нельзя доверять данным из пользовательского ввода без экранирования.

Решение: Использовать префикс для переменных (например, добавить проверку на ключи) или применять метод assign с последующей передачей. Для экранирования вывода использовать htmlspecialchars() в шаблоне.

Как реализовать шаблонизатор в виде класса с методами assign() и render()?

Объектно-ориентированный подход позволяет инкапсулировать данные и управлять состоянием. Класс хранит массив переменных, метод assign() добавляет переменную, render() подключает шаблон с буферизацией.

class Template {
    private $data = [];

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

    public function render($template) {
        extract($this->data);
        ob_start();
        include $template;
        return ob_get_clean();
    }
}

// Использование
$tpl = new Template();
$tpl->assign('name', 'Иван');
$tpl->assign('items', ['Пункт1', 'Пункт2']);
echo $tpl->render('list.tpl');
<!-- list.tpl -->
<ul>
<?php foreach ($items as $item): ?>
    <li><?= $item ?></li>
<?php endforeach; ?>
</ul>

Проблема: Класс не фильтрует данные. При выводе пользовательского контента в шаблоне необходимо явно вызывать htmlspecialchars(). Метод assign() не защищает от переопределения уже установленных переменных (перезапись по ключу).

Решение: Ввести метод assignArray() для массовой передачи, проверять существование ключа и выдавать ошибку. Для экранирования добавить вспомогательную функцию e().

Расширенные примеры шаблонов в PHP

Рассмотрим более сложный пример шаблонизатора с поддержкой макетов (layout) и вложенных шаблонов.

Как создать шаблонизатор с наследованием и блоками?

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

Пример
class LayoutTemplate {
    private $blocks = [];
    private $data = [];

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

    public function startBlock($name) {
        ob_start();
        $this->blocks[$name] = '';
    }

    public function endBlock($name) {
        $this->blocks[$name] = ob_get_clean();
    }

    public function render($template) {
        extract($this->data);
        ob_start();
        include $template;
        $childContent = ob_get_clean();
        extract($this->blocks);
        ob_start();
        include 'layout.tpl';
        return ob_get_clean();
    }
}
Пример
<!-- layout.tpl -->
<!DOCTYPE html>
<html>
<head><title><?= $title ?? 'Default' ?></title></head>
<body>
    <header><?= $headerBlock ?? '' ?></header>
    <main><?= $contentBlock ?? '' ?></main>
    <footer><?= $footerBlock ?? '' ?></footer>
</body>
</html>
Пример
<!-- index.tpl (дочерний) -->
<?php $this->startBlock('headerBlock'); ?>
    <h1>Добро пожаловать</h1>
<?php $this->endBlock('headerBlock'); ?>

<?php $this->startBlock('contentBlock'); ?>
    <p>Основное содержимое.</p>
<?php $this->endBlock('contentBlock'); ?>
Пример
// index.php
$tpl = new LayoutTemplate();
$tpl->assign('title', 'Мой сайт');
echo $tpl->render('index.tpl');
<!DOCTYPE html>
<html>
<head><title>Мой сайт</title></head>
<body>
    <header><h1>Добро пожаловать</h1></header>
    <main><p>Основное содержимое.</p></main>
    <footer></footer>
</body>
</html>

Проблема: Реализация сложна для новичков, требуется понимание буферизации и архитектуры. Возможны ошибки с вложенными блоками. Метод startBlock/endBlock должен вызываться строго парами.

Решение: Использовать готовые шаблонизаторы типа Twig, которые предоставляют блоки и наследование из коробки. Либо упростить собственную реализацию, используя только один блок (например, контент).

Как работать с массивами данных и условиями в шаблонах?

В шаблонах часто требуется вывести список элементов или показать контент в зависимости от условия. Пример с foreach и if.

Пример
// data.php
$users = [
    ['name' => 'Анна', 'age' => 25],
    ['name' => 'Борис', 'age' => 30],
];
include 'users.tpl';
Пример
<!-- users.tpl -->
<ul>
<?php foreach ($users as $user): ?>
    <li>
        <?= htmlspecialchars($user['name']) ?> 
        (<?= $user['age'] ?> лет)
    </li>
<?php endforeach; ?>
</ul>
<ul>
    <li>Анна (25 лет)</li>
    <li>Борис (30 лет)</li>
</ul>

Проблема: Забыли экранировать имя пользователя. Если имя содержит HTML-теги, они будут выполнены. Исправление: использовать htmlspecialchars.

Решение: Создать вспомогательную функцию e() в шаблоне, например: <?php function e($v) { return htmlspecialchars($v, ENT_QUOTES, 'UTF-8'); } ?> и использовать <?= e($user['name']) ?>.

Шаблоны (tpl) в PHP - comments

En
Index php tpl (php)