Управление шаблонами: реализация 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() }} для включения содержимого родительского блока. Это мощный приём для расширения шаблонов.