Управление шаблонами: реализация layout

Раздел: Фронтенд-разработка -> Шаблонизация

Настройка макета (layout) в PHP через index.php позволяет создавать единый каркас страницы, повторно используя общие компоненты: шапку, подвал, меню. Это улучшает поддержку проекта и отделяет логику представления от бизнес-логики. Рассмотрим несколько способов реализации, от простых до более гибких.

Основное решение: функция render с буферизацией

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

Пример базовой структуры:


// index.php
function render($layout, $view, $data = []) {
    extract($data);
    ob_start();
    include "views/$view.php";
    $content = ob_get_clean();
    include "layouts/$layout.php";
}

$data = ['title' => 'Главная страница', 'items' => ['A', 'B']];
render('main', 'home', $data);

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

Файл layouts/main.php:


<!DOCTYPE html>
<html>
<head><title><?= $title ?? 'Default' ?></title></head>
<body>
    <header><?php include 'partials/header.php' ?></header>
    <main><?= $content ?></main>
    <footer><?php include 'partials/footer.php' ?></footer>
</body>
</html>

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

Файл views/home.php:


<h1><?= $title ?></h1>
<ul>
<?php foreach ($items as $item): ?>
    <li><?= $item ?></li>
<?php endforeach; ?>
</ul>

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

Типичные проблемы:

  • Конфликт имен переменных: extract($data) может перезаписать существующие переменные. Используйте префиксы или проверяйте.
  • Ошибки вложенной буферизации: если внутри view начинается ob_start, необходимо правильно управлять вложенностью.
  • Некорректные пути: пути к файлам должны быть относительно index.php. Рекомендуется определить константу ROOT.

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

Варианты реализации

Как сделать layout с помощью простого include?

Для мини-проектов можно включить layout напрямую в индексный файл.


// index.php
$title = 'О нас';
include 'layouts/simple.php';

Файл simple.php подключает шапку, контент (включая content.php), подвал. Недостаток: контент не отделён, переменные глобальны.

Проблемы: дублирование кода при изменении структуры; сложность передачи данных; переменные доступны во всех подключаемых файлах, что может вызвать конфликты.

Как отделить контент от layout через буферизацию вручную?

Можно использовать ob_start и ob_get_clean непосредственно в index.php без функции.


// index.php
$pageTitle = 'Контакты';
ob_start();
include 'views/contacts.php';
$content = ob_get_clean();
include 'layouts/main.php';

Этот способ даёт больше контроля, но требует повторения кода на каждой странице.

Проблемы: легко забыть начать буферизацию или очистить её; при множестве страниц дублирование кода.

Как передавать данные в layout без глобальных переменных?

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


function renderView($view, $data, $layout = 'main') {
    extract($data);
    ob_start();
    include "views/$view.php";
    $content = ob_get_clean();
    include "layouts/$layout.php";
}

Это позволяет переопределять layout для конкретных страниц (например, для админки).

Проблема: если в $data есть ключ 'content', он перезапишет сгенерированный контент. Используйте защиту: unset($data['content']) до extract или используйте другую переменную.

Как реализовать layout через шаблонизатор Twig?

Twig предоставляет мощное наследование шаблонов. Установите через composer, настройте загрузчик.


// index.php
require_once 'vendor/autoload.php';

$loader = new \Twig\Loader\FilesystemLoader('templates');
$twig = new \Twig\Environment($loader, [
    'cache' => 'cache',
    'auto_reload' => true,
]);

echo $twig->render('page.twig', ['title' => 'О сайте', 'menu' => ['Главная', 'Контакты']]);

Шаблон base.twig:


<!DOCTYPE html>
<html>
<head><title>{% block title %}Default{% endblock %}</title></head>
<body>
    <header>{% include 'header.twig' %}</header>
    <main>{% block content %}{% endblock %}</main>
    <footer>{% include 'footer.twig' %}</footer>
</body>
</html>

page.twig:


{% extends 'base.twig' %}

{% block title %}{{ title }}{% endblock %}

{% block content %}
<h1>{{ title }}</h1>
<ul>
{% for item in menu %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% endblock %}

Типичные ошибки: не настроен autoload; пути к шаблонам неверны; включение кэша без автоматической перезагрузки (рекомендуется auto_reload в разработке); забыли экранировать вывод (Twig экранирует по умолчанию, но для сырого HTML используйте raw).

Цели и случаи использования

Выбор метода зависит от масштаба проекта:

  • Простые include – для одностраничных сайтов или быстрого прототипирования.
  • Буферизация с функцией – для проектов средней сложности, где нужна гибкость, но без внешних зависимостей.
  • Twig – для крупных проектов с командой разработчиков, высокими требованиями к безопасности и читаемости шаблонов.

Пример 1: Детальный разбор буферизации с частичными шаблонами

Рассмотрим полную структуру из трёх файлов: index.php, layout.php, view.php. Также добавим partials для шапки и подвала с динамическими данными.

Код index.php:

Пример

<?php
function render($layout, $view, $data = []) {
    extract($data);
    ob_start();
    include "views/$view.php";
    $content = ob_get_clean();
    include "layouts/$layout.php";
}

$siteData = [
    'title' => 'Пример layout',
    'menu'  => ['Главная', 'Блог', 'Контакты'],
    'user'  => 'Администратор',
];

render('main', 'home', $siteData);

Файл layouts/main.php:

Пример

<!DOCTYPE html>
<html>
<head>
    <title><?= $title ?? 'Сайт' ?></title>
</head>
<body>
    <?php include 'partials/header.php'; ?>
    <main>
        <?= $content; ?>
    </main>
    <?php include 'partials/footer.php'; ?>
</body>
</html>

Файл partials/header.php:

Пример

<header>
    <nav>
        <ul>
            <?php foreach ($menu as $item): ?>
                <li><?= $item ?></li>
            <?php endforeach; ?>
        </ul>
    </nav>
    <div class="user">Привет, <?= $user ?? 'гость' ?>!</div>
</header>

Файл views/home.php:

Пример

<h1><?= $title ?></h1>
<p>Добро пожаловать на сайт.</p>

Результат вывода (HTML):

<!DOCTYPE html>
<html>
<head><title>Пример layout</title></head>
<body>
    <header>
        <nav>
            <ul>
                <li>Главная</li>
                <li>Блог</li>
                <li>Контакты</li>
            </ul>
        </nav>
        <div class="user">Привет, Администратор!</div>
    </header>
    <main>
        <h1>Пример layout</h1>
        <p>Добро пожаловать на сайт.</p>
    </main>
</body>
</html>

Заметим, что переменные из $data доступны во всех подключаемых файлах благодаря extract. Это удобно, но следует избегать конфликтов имён.

Пример 2: Наследование layout в Twig с дополнительными блоками

Используем Twig для создания сложной структуры с несколькими блоками. Файлы: base.twig, page.twig, index.php.

Код index.php:

Пример

<?php
require_once 'vendor/autoload.php';

$loader = new \Twig\Loader\FilesystemLoader('templates');
$twig = new \Twig\Environment($loader, [
    'cache' => false,
]);

$data = [
    'title' => 'Страница контактов',
    'phone' => '+7 123 456-78-90',
    'email' => 'info@example.com',
    'sidebar' => ['пункт1', 'пункт2', 'пункт3'],
];

echo $twig->render('page.twig', $data);

Шаблон templates/base.twig:

Пример

<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}Default title{% endblock %}</title>
    {% block stylesheets %}{% endblock %}
</head>
<body>
    <div id="wrapper">
        {% block header %}
            <header>Шапка сайта</header>
        {% endblock %}
        <div class="container">
            <aside>
                {% block sidebar %}
                    <ul>
                        {% for item in sidebar|default([]) %}
                            <li>{{ item }}</li>
                        {% endfor %}
                    </ul>
                {% endblock %}
            </aside>
            <main>
                {% block content %}{% endblock %}
            </main>
        </div>
        {% block footer %}
            <footer>Подвал сайта</footer>
        {% endblock %}
    </div>
    {% block javascripts %}{% endblock %}
</body>
</html>

Шаблон templates/page.twig:

Пример

{% extends 'base.twig' %}

{% block title %}{{ title }}{% endblock %}

{% block content %}
    <h1>{{ title }}</h1>
    <p>Телефон: {{ phone }}</p>
    <p>Email: {{ email }}</p>
{% endblock %}

{% block sidebar %}
    {{ parent() }}
    <hr>
    <p>Дополнительная боковая панель</p>
{% endblock %}

Результат вывода (сокращённый):

<!DOCTYPE html>
<html>
<head><title>Страница контактов</title></head>
<body>
    <div id="wrapper">
        <header>Шапка сайта</header>
        <div class="container">
            <aside>
                <ul>
                    <li>пункт1</li>
                    <li>пункт2</li>
                    <li>пункт3</li>
                </ul>
                <hr>
                <p>Дополнительная боковая панель</p>
            </aside>
            <main>
                <h1>Страница контактов</h1>
                <p>Телефон: +7 123 456-78-90</p>
                <p>Email: info@example.com</p>
            </main>
        </div>
        <footer>Подвал сайта</footer>
    </div>
</body>
</html>

Обратите внимание на использование {{ parent() }} для включения содержимого родительского блока. Это мощный приём для расширения шаблонов.

Настройка макета (layout) в PHP - comments

En
Index php option layout (php)