Способы построения блоков в PHP для систем шаблонов

Раздел: -> Шаблоны PHP

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

Какое решение обеспечивает гибкость и повторное использование блоков без внешних библиотек?

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

Пример базового класса Block:


class Block {
    private $blocks = [];
    public function start($name) {
        ob_start();
        $this->blocks[$name] = '';
    }
    public function end($name) {
        $this->blocks[$name] = ob_get_clean();
    }
    public function get($name) {
        return $this->blocks[$name] ?? '';
    }
    public function show($name) {
        echo $this->get($name);
    }
}
  

Использование в шаблоне:


<?php $block = new Block(); ?>
<?php $block->start('header'); ?>
  <header>Шапка сайта</header>
<?php $block->end('header'); ?>
...
<?php $block->show('header'); ?>
  

Цель: создание независимых блоков, которые можно вызывать в любом месте шаблона, а также переопределять при наследовании.

Типичные ошибки:

  • Забыть вызвать end() после start() – буфер остаётся открытым, что приводит к непредсказуемому выводу.
  • Одновременный запуск нескольких блоков без завершения предыдущего – буферизация вложенных вызовов требует дополнительного управления (например, стек).

Как разделить страницу на подключаемые части без буферизации?

Простейший вариант – использование директив include и require. Каждый блок выносится в отдельный файл и подключается по необходимости.


// header.php
<header><h1>Мой сайт</h1></header>

// main.php
<?php require 'header.php'; ?>
<main>Основное содержимое</main>
<?php require 'footer.php'; ?>
  

Цель: быстрое разделение кода на логические части без написания классов. Подходит для простых проектов.

Проблемы: переменные, объявленные до подключения, доступны внутри блока, что может вызвать конфликты имён. Невозможно переопределить блок при наследовании.

Как создать многократно используемые блоки через функции?

Функции позволяют инкапсулировать вывод блока и принимать параметры.


function block_menu($items) {
    echo '<nav><ul>';
    foreach ($items as $item) {
        echo '<li>' . htmlspecialchars($item) . '</li>';
    }
    echo '</ul></nav>';
}
// вызов
block_menu(['Главная', 'О нас', 'Контакты']);
  

Цель: переиспользование блоков с разными данными. Удобно для меню, виджетов.

Недостатки: нет возможности накапливать содержимое между вызовами, сложно реализовать наследование блоков.

Как реализовать систему наследования блоков, как в Twig или Blade?

Можно создать простой шаблонизатор, который поддерживает замену блоков. Пример на основе классов и буферизации.


class Template {
    private $blocks = [];
    private $extends = null;
    public function block($name, $content = null) {
        if ($content !== null) {
            $this->blocks[$name] = $content;
        } else {
            ob_start();
        }
    }
    public function endblock() {
        $name = array_key_last($this->blocks);
        $this->blocks[$name] = ob_get_clean();
    }
    public function parent() {
        // предполагается, что родительский блок сохранён
        return $this->parentContent ?? '';
    }
    public function render($file) {
        extract($this->blocks);
        require $file;
    }
}
  

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

Сложность: требуется аккуратное управление стеком блоков, иначе легко получить ошибки из-за неправильной вложенности.

Дополнительные примеры демонстрируют нестандартные приёмы работы с блоками в PHP.

Пример 1. Вложенные блоки с использованием стека

Класс, поддерживающий вложенные блоки и автоматическое завершение

Пример

class AdvancedBlock {
    private $stack = [];
    private $blocks = [];
    public function start($name) {
        array_push($this->stack, $name);
        ob_start();
    }
    public function end() {
        $name = array_pop($this->stack);
        $this->blocks[$name] = ob_get_clean();
        if (!empty($this->stack)) {
            // вложенный блок автоматически вставляется в родительский
            echo $this->blocks[$name];
        }
    }
    public function get($name) {
        return $this->blocks[$name] ?? '';
    }
}

// использование
$ab = new AdvancedBlock();
$ab->start('outer');
  echo 'Начало внешнего блока';
  $ab->start('inner');
    echo 'Внутренний контент';
  $ab->end(); // inner
  echo 'Конец внешнего';
$ab->end(); // outer
echo $ab->get('outer');
Начало внешнего блокаВнутренний контентКонец внешнего

Пример 2. Передача параметров в блок через замыкания

Блок может быть анонимной функцией, что позволяет передавать данные без глобального состояния

Пример

$blocks = [];
$blocks['card'] = function($title, $body) {
    return "<div class='card'><h3>{$title}</h3><p>{$body}</p></div>";
};

$html = '';
$html .= $blocks['card']('Заголовок', 'Текст карточки');
$html .= $blocks['card']('Ещё один', 'Другой текст');
echo $html;
<div class='card'><h3>Заголовок</h3><p>Текст карточки</p></div>
<div class='card'><h3>Ещё один</h3><p>Другой текст</p></div>

Пример 3. Использование статических методов для реестра блоков

Единый реестр блоков, доступный из любого места приложения

Пример

class BlockRegistry {
    private static $blocks = [];
    public static function register($name, callable $renderer) {
        self::$blocks[$name] = $renderer;
    }
    public static function render($name, ...$args) {
        if (!isset(self::$blocks[$name])) {
            throw new Exception("Блок '$name' не найден");
        }
        return call_user_func_array(self::$blocks[$name], $args);
    }
}

BlockRegistry::register('alert', function($type, $message) {
    return "<div class='alert alert-{$type}'>{$message}</div>";
});

echo BlockRegistry::render('alert', 'success', 'Операция выполнена!');
<div class='alert alert-success'>Операция выполнена!</div>

Пример 4. Кеширование блоков для повышения производительности

Сохраняем результат работы блока в файл, чтобы не генерировать его при каждом запросе

Пример

function cached_block($name, $ttl = 3600) {
    $cacheFile = __DIR__ . '/cache/' . md5($name) . '.html';
    if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $ttl)) {
        return file_get_contents($cacheFile);
    }
    ob_start();
    // симуляция сложной генерации блока
    echo "<!-- Блок сгенерирован в ".date('H:i:s')." -->";
    $content = ob_get_clean();
    file_put_contents($cacheFile, $content);
    return $content;
}

echo cached_block('footer');
echo cached_block('footer'); // возьмётся из кеша, если время не истекло
<!-- Блок сгенерирован в 14:23:05 -->
<!-- Блок сгенерирован в 14:23:05 --> (из кеша)

Использование блоков в PHP - comments

En
Php block (php)