Шаблоны header и footer: эффективные приёмы в PHP
Обзор шаблонов header и footer в PHP
При разработке веб-приложений на PHP часто возникает необходимость повторно использовать общие элементы страниц: шапку (header) и подвал (footer). Грамотное отделение этих частей от основного контента упрощает поддержку и изменение дизайна сайта. Рассмотрим несколько подходов к реализации шаблонов header.php и footer.php, их преимущества и недостатки.
Основной подход: подключение через include / require
Наиболее распространённый и эффективный способ для небольших и средних проектов - вынести код шапки и подвала в отдельные файлы и подключать их в основном шаблоне. Это позволяет менять внешний вид на всех страницах, изменив всего два файла.
<!-- header.php -->
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title><?= $title ?? 'Мой сайт' ?></title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<header>
<h1>Заголовок сайта</h1>
<nav><!-- навигация --></nav>
</header>Index php tpl (шаблоны (tpl) в php)
<!-- footer.php -->
<footer>
<p>© 2025 Мой сайт</p>
</footer>
</body>
</html>Header php footer php (шаблоны header и footer в php)
<!-- index.php -->
<?php
$title = 'Главная страница';
require 'header.php';
?>
<main>
<h2>Добро пожаловать</h2>
<p>Основной контент страницы.</p>
</main>
<?php require 'footer.php'; ?>Index php option layout (настройка макета (layout) в php)
Типичные ошибки и их решение
- Ошибка путей. Если файлы шаблона лежат в подпапке, require может не найти их. Решение: использовать абсолютный путь через __DIR__ или константу ROOT_PATH:
require __DIR__ . '/../templates/header.php'; - Дублирование подключения. Если один и тот же файл подключается дважды, могут возникнуть конфликты (например, повторное объявление функции). Используйте require_once или include_once.
- Область видимости переменных. Переменные, объявленные до подключения, доступны внутри подключаемого файла, но не наоборот. Для передачи данных лучше использовать локальные переменные или массивы.
Как сделать так, чтобы переменная $title из основного файла автоматически отображалась в заголовке страницы? Ответ - передавать её до вызова require, как в примере выше. Это один из способов динамической настройки header.
Вариант 1: Функция для вывода header и footer
Как организовать вывод шапки и подвала, чтобы не писать каждый раз require в каждом файле?
Создаются функции, которые инкапсулируют подключение и позволяют передавать параметры в виде массива.
<?php
function renderHeader(array $data = []) {
$title = $data['title'] ?? 'Default';
$css = $data['css'] ?? '/css/style.css';
include 'header.php'; // header.php использует $title, $css
}
function renderFooter() {
include 'footer.php';
}
?>
<!-- Использование -->
<?php
renderHeader(['title' => 'О нас', 'css' => '/css/about.css']);
?>
<main>...</main>
<?php renderFooter(); ?>
Проблема: глобальное загрязнение переменными.
Внутри header.php переменные $title и $css извлекаются из локальной области видимости функции, но если в основном коде есть такие же переменные, конфликтов не будет, так как они изолированы. Однако нужно следить за именами параметров.
Вариант 2: Буферизация вывода и шаблонизаторы
Как отделить логику от представления и поддерживать сложные вложенные шаблоны?
Используются шаблонизаторы (Twig, Smarty) или встроенная буферизация с помощью ob_start().
<?php
ob_start();
?>
<main>
<h2>Контент</h2>
</main>
<?php
$content = ob_get_clean();
require 'layout.php'; // layout содержит header, footer и место для $content
?>
В layout.php шапка и подвал уже статичны, а в середине вставлено <?= $content ?>. Это проще, чем компоновать отдельные файлы.
Проблема: уровень вложенности и отладка.
При активной буферизации сложно отследить, где начинается и заканчивается буфер. Рекомендуется использовать готовые шаблонизаторы, которые решают эту проблему.
Вариант 3: Объектно-ориентированный подход
Как сделать систему шаблонов гибкой и расширяемой для крупных проектов?
Создаётся класс Template, который загружает файлы шаблонов и подставляет данные.
<?php
class Template {
private string $basePath;
public function __construct(string $basePath = '') {
$this->basePath = $basePath;
}
public function render(string $__template, array $__data = []): void {
extract($__data);
include $this->basePath . '/' . $__template . '.php';
}
}
// Использование
$tpl = new Template(__DIR__ . '/templates');
$tpl->render('header', ['title' => 'Контакты']);
?>
<main>...</main>
<?php $tpl->render('footer'); ?>
Этот подход легко масштабируется: можно добавить методы для вложенных шаблонов, кэширования и т.д.
Проблема: производительность при большом количестве вызовов extract().
Это незначительно, но в высоконагруженных системах лучше передавать данные напрямую через $this->data внутри шаблона.
Вариант 4: Использование include с проверкой на существование файла
Как избежать ошибок при отсутствии файла header.php?
Применяется file_exists() перед подключением или условный include без ошибки.
<?php
$headerFile = 'header.php';
if (!file_exists($headerFile)) {
// логирование или альтернативный контент
echo '<!-- header not found -->';
} else {
include $headerFile;
}
?>
Этот вариант уместен, когда шаблоны могут отсутствовать в некоторых окружениях (например, при разработке модульной системы).
Проблема: дублирование кода проверки в каждом файле.
Решение: вынести проверку в функцию или использовать контейнер, который гарантирует наличие шаблона.
Расширенные примеры использования шаблонов header и footer
Пример 1: Динамический header с разными стилями для разных страниц
Часто требуется, чтобы шапка страницы имела уникальный класс для выделения текущего раздела. Реализуем через передачу ассоциативного массива.
<!-- header.php -->
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($title) ?></title>
<link rel="stylesheet" href="<?= $cssFile ?>">
</head>
<body class="<?= $bodyClass ?? '' ?>">
<header class="<?= $headerClass ?? 'main-header' ?>">
<nav>
<a href="/" class="<?= $currentPage === 'home' ? 'active' : '' ?>">Главная</a>
<a href="/about" class="<?= $currentPage === 'about' ? 'active' : '' ?>">О нас</a>
</nav>
</header>
<!-- about.php -->
<?php
$data = [
'title' => 'О компании',
'cssFile' => '/css/about.css',
'bodyClass' => 'about-page',
'headerClass' => 'header-about',
'currentPage' => 'about'
];
extract($data);
include 'header.php';
?>
<main>...</main>
<?php include 'footer.php'; ?>
Результат: в браузере отобразится страница с заголовком "О компании", подключён about.css, body получит класс about-page, а ссылка "О нас" будет подсвечена классом active.
Пример 2: Вложенные шаблоны с буферизацией (layout + content)
Создадим файл layout.php, который будет содержать общую обёртку, а контент будем подставлять через переменную $content.
<!-- layout.php -->
<!DOCTYPE html>
<html>
<head>
<title><?= $title ?? 'Без названия' ?></title>
<link rel="stylesheet" href="/css/<?= $style ?? 'main' ?>.css">
</head>
<body>
<header>...</header>
<main><?= $content ?></main>
<footer>...</footer>
</body>
</html>
<!-- page.php, который использует layout -->
<?php
$title = 'Галерея';
$style = 'gallery';
ob_start();
?>
<section class="gallery">
<img src="photo1.jpg" alt="Фото 1">
<img src="photo2.jpg" alt="Фото 2">
</section>
<?php
$content = ob_get_clean();
include 'layout.php';
?>
Результат: страница содержит header, footer и вставленный блок с галереей. Стили из gallery.css подключаются в head.
Пример 3: Многоуровневое наследование шаблонов с помощью Twig
Хотя Twig не является встроенной функцией PHP, его часто используют в современных проектах. Покажем, как с его помощью организовать header и footer.
<!-- base.html.twig -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Мой сайт{% endblock %}</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<header>{% block header %}Шапка по умолчанию{% endblock %}</header>
<main>{% block content %}{% endblock %}</main>
<footer>{% block footer %}Подвал по умолчанию{% endblock %}</footer>
</body>
</html>
<!-- index.html.twig, расширяющий base -->
{% extends 'base.html.twig' %}
{% block title %}Главная{% endblock %}
{% block header %}
<h1 class="fw-bold">Приветствую!</h1>
{% endblock %}
{% block content %}
<p>Основной контент.</p>
{% endblock %}
Результат: страница получает заголовок "Главная", header заменён на кастомный, остальное - из базового шаблона. Файлы шаблонов обрабатываются Twig.
Пример 4: Автоматическая подстановка мета-тегов через массив данных
Расширим стандартный header.php для поддержки переменного количества мета-тегов.
<!-- header.php -->
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title><?= $title ?></title>
<?php if (!empty($meta)): ?>
<?php foreach ($meta as $name => $content): ?>
<meta name="<?= htmlspecialchars($name) ?>" content="<?= htmlspecialchars($content) ?>">
<?php endforeach; ?>
<?php endif; ?>
<?php if (!empty($styles)): ?>
<?php foreach ($styles as $href): ?>
<link rel="stylesheet" href="<?= $href ?>">
<?php endforeach; ?>
<?php endif; ?>
</head>
<body>
<header>...</header>
<!-- page.php -->
<?php
$data = [
'title' => 'Контакты',
'meta' => [
'description' => 'Страница контактов компании',
'keywords' => 'контакты, телефон, email'
],
'styles' => ['/css/contact.css', '/css/icons.css']
];
extract($data);
include 'header.php';
?>
<main>...</main>
<?php include 'footer.php'; ?>
Результат: в head появятся два meta-тега и два link для CSS.
Пример 5: Использование include_once для предотвращения повторного подключения библиотек
Если в header.php подключается файл с функциями, а footer.php также его содержит, возникает ошибка переопределения. Решение - include_once.
<!-- functions.php -->
<?php
function getCopyrightYear(): string {
return '2005-' . date('Y');
}
?>
<!-- header.php -->
<?php include_once 'functions.php'; ?>
<!DOCTYPE html>...</pre>
<!-- footer.php -->
<?php include_once 'functions.php'; ?>
<footer>© <?= getCopyrightYear() ?> Моя компания</footer>
Ошибки не возникает, так как второй раз файл не подключается.
Пример 6: Передача переменных из сессии для отображения статуса пользователя в header
<!-- header.php -->
...
<header>
<div class="user-info">
<?php if (isset($_SESSION['user'])): ?>
<span>Привет, <?= htmlspecialchars($_SESSION['user']['name']) ?>!</span>
<a href="/logout">Выход</a>
<?php else: ?>
<a href="/login">Войти</a>
<?php endif; ?>
</div>
</header>
При залогиненном пользователе в шапке отображается его имя и ссылка на выход.