PHP умеет встраиваться в HTML. Это очень классная возможность, которой, однако, не следует злоупотреблять. Смешивая код логики и код генерации HTML вы получите совершенно отвратительнй результат, за который вам будет стыдно, когда вы немного продвинетесь в изучении веб-разработки.
Чтобы получить отличный результат, генерацию HTML следует отделять от остальной логики приложения. Сделать это можно с помощью прекрасной возможности PHP подключать файлы.
Для начала надо условиться, что подключаемый файл будет больше HTML-файлом чем PHP. То есть
PHP-код будет встраиваться в HTML, а не наоборот. Вся разметка будет написана как есть (а не через echo
).
Следующее условие – использование альтернативного синтаксиса условных операторов и циклов. Это нужно для того, чтобы шаблоны было легко читать, ведь искать в простыне HTML-кода закрывающую фигурную скобочку очень сложно.
Кроме этого важно отказаться от сложных манипуляций с данными в шаблоне. Если данные надо подготовить для вывода, то это надо сделать перед подключением шаблона.
Вот небольшой пример такого шаблона.
<h1>Статьи</h1>
<?php foreach($posts as $post): ?>
<div>
<h2><?= htmlspecialchars($post->title) ?></h2>
<div>
<?= $post->htmlContent ?>
</div>
</div>
<?php endforeach ?>
Подключить такой шаблон можно так.
<?php
//...
$posts = $this->db->table('posts')->select();
require __DIR__ . '/posts.phtml';
Кроме прочего я использую расширение .phtml для файлов шаблонов, чтобы отличать их от других;
Использую шорттег <?=
(он предназначен специально для таких случаев и работает даже если шортеги отключены);
Не ставлю точку с запятой перед закрывающим дескриптором PHP. Это необязательные условия отличного результата,
но вы можете скопировать мой стиль.
Не забывайте, что текст и значения атрибутов надо экранировать. Функция htmlspecialchars отлично подходит для этого. Но учтите, что по умолчанию она не экранирует одинарные кавычки. Если вы используете двойные кавычки в HTML, то проблем не будет, а если одинарные, не забудьте заэкранировать их.
Избегайте подстановок переменных в скрипты, будьте внимательны с атрибутами, значения которых, могут быть не просто текстом.
<a href="<?= htmlspecialchars($link) ?>">клёвая ссылка</a>
<!-- если $link будет иметь значение 'javascript: alert('XSS')', то будет совсем не клёво -->
Простое подключение имеет несколько недостатков. Один из них заключается в том, что полученный HTML не всегда надо вывести немедленно. Решить эту проблему помогут функции контроля вывода. Обязательно разберитесь как они работают.
Теперь можно написать функцию, которая будет подключать и файл и захватывать его вывод в буфер.
<?php
/**
* Подключение файла с буферизацией вывода
*/
function ob_include(string $file): string
{
ob_start();
require $file;
return ob_get_clean();
}
Функция ob_include
сейчас не работает. Как только мы переместили require
внутрь функции,
область видимости в подключённом шаблоне стала такой же как внутри функции. Но это легко исправить с помощью
extract.
<?php
/**
* Подключение файла с буферизацией вывода
*/
function ob_include(string $file, array $params): string
{
extract($params);
ob_start();
require $file;
return ob_get_clean();
}
Теперь в функцию можно передать ассоциативный массив значения из которого будут доступны в подключаемом шаблоне.
<?php
//...
$posts = $this->db->table('posts')->select();
$html = ob_include(__DIR__ . '/posts.phtml', ['posts' => $posts]);
echo $html;
Возможно вы уже обратили внимание, что в текущей реализации есть ошибка. Переменные $file
и $params
тоже попадают в область видимости шаблона.
Кроме того, если что-нибудь передать в $params['file']
, то $file
перезапишется. Исправим это.
/**
* Подключение файла с буферизацией вывода
* @param string $file
* @param array $params
*/
function ob_include(): string
{
extract(func_get_arg(1));
ob_start();
require func_get_arg(0);
return ob_get_clean();
}
Готово! Никаких лишних переменных. Используйте эту функцию как есть или переделайте в более подходящий вам вариант.
Вызывать ob_include
внутри шаблона, по-моему, не самая хорошая идея (хотя ничто не мешает вам сделать так), но можно передавать
результаты одного вызова ob_include
в параметры другого.
<?php
// ...
$posts = $this->db->table('posts')->select();
$postsHtml = ob_include(__DIR__ . '/posts.phtml', ['posts' => $posts]);
$html = ob_include(__DIR__ . '/layout.phtml', ['content' => $postsHtml]);
echo $html;
При желании можно завернуть вызов ob_include
в метод класса и реализовать интерфейс вроде следующего.
<?php
// ...
$posts = $this->db->table('posts')->select();
echo $layout // $layout инстанцирует какой-нибудь код уровнем выше
->setTitle('Публикации')
->addJs('/popup.js')
->addContent(__DIR__ . '/posts.phtml', ['posts' => $posts])
->render();
Со временем вы познакомитесь с более мощными библиотеками шаблонизации для PHP, но этот простой подход ещё не раз выручит вас там, где нужно просто сгенерировать HTML-документ.