Сначала - зачем нужен шаблонизатор. Тут подмена понятий. Скорее вопрос такой: как отделить вывод от заголовков. Если не отделять, то может быть такая ошибка
<html>
<body>
<?php
// начать сессию
session_start(); // отправить куку PHPSESSID через заголовки HTTP
// но она не может отправиться, потому что уже начался вывод HTML в строке "<html ..."
// если не авторизован, то отправить на страницу логина
if (empty($_SESSION['username']))
header('Location: /login.php'); // но заголовок HTTP тоже не может отправиться, как и кука
?>
<h1>Hello, <?php echo $_SESSION['username'] ?></h1>
</body>
</html>
Проблема решается, если вывод HTML делать после вывода заголовков. Например, использовать буфер
<?php ob_start(); // открыть буфер ?>
<html>
<body>
<?php
session_start(); // кука PHPSESSID отправится, потому что HTML ещё в буфере
if (empty($_SESSION['username']))
header('Location: /login.php'); // заголовок HTTP отправится, потому что HTML ещё в буфере
?>
<h1>Hello, <?php echo $_SESSION['username'] ?></h1>
</body>
</html>
<?php ob_end_flush(); // выбросить содержимое буфера наружу и закрыть его ?>
Однако так придётся писать в каждом месте, где формируется HTML. Можно ли сократить?
Простейшее представление через буфер
<?php
function render($viewPath)
{
if (!is_file($viewPath))
return 'View "'. $viewPath . '" not exists';
ob_start();
include($viewPath);
return ob_get_clean();
}
session_start();
if (empty($_SESSION['username']))
header('Location: /login.php');
$viewsPath = __DIR__.'/views/'; // где лежат представления
render($viewsPath . 'page.php'); // нарисовать страницу HTML
<?php
# page page.php
?>
<html>
<body>
<h1>Hello, <?php echo $_SESSION['username'] ?></h1>
</body>
</html>
А дополнительно ещё решают проблему отделения логики от формирования интерфейса. Работа с внешними данными в одном месте, а отображение их - в другом. Для этого в представление передают всё, что нужно показать. И точка. Лишних данных там не надо. В некоторых фреймворках стоит Exception если представление начнёт работать с базой данных, читает данные из входного запроса или пытается отправить заголовки.
<?php
function render($viewPath, $vars)
{
if (!is_file($viewPath))
return 'View "'. $viewPath . '" not exists';
extract($vars); // extract делает из массива набор переменных в локальной области видимости
ob_start();
include($viewPath); // эти переменные будут видны внутри подключаемого файла
return ob_get_clean();
}
session_start();
if (empty($_SESSION['username']))
header('Location: /login.php');
$viewsPath = __DIR__.'/views/'; // где лежат представления
render($viewsPath . 'page.php', array( // отображаемые данные передаются массивом
'username' => $_SESSION['username'],
));
# page.php
<?php
/**
* Подсказки для IDE, чтобы не подсвечивал переменные как неопределённые
* @var string $username
*/
<html>
<body>
<h1>Hello, <?php echo $sername ?></h1>
</body>
</html>
Не правда ли, с представлениями код становится значительно изящнее. А если логика для реализации представлений хранится в отдельном файле-библиотеке, то код становится короче и понятнее.
В фреймворках вместо функции render может использоваться объектная реализация
<?php
class ViewException extends Exception {}
class View
{
public $viewsPath = __DIR__.'/views/';
public function __construct($viewsPath = null)
{
// настройка представлений
// например, можно перепределить место хранения представлений
if (!is_null($viewsPath))
$this->viewsPath = $viewsPath;
}
public function render($viewPath, $vars)
{
if (!is_file($this->viewsPath . $viewPath))
throw new ViewException('View "'. $viewPath . '" in folder "'. $this->viewsPath . '" not exists');
extract($vars);
ob_start();
include($this->viewsPath . $viewPath);
return ob_get_clean();
}
}
$view = new View();
$view->render('page.php', array(
'username' => $_SESSION['username']
));
Как таблицы шаблонизаирова если в одной 3 столбца, в другой 5?
Никто в представлениях не запрещает использовать языковые конструкции. Это могут быть конструкции языка PHP или какой-нибудь другой язык, специально написанный для шаблонизатора. Например, в Smarty, Blade, Pug свои языки. Передай в представление количество колонок и сделай цикл :)
у первой первый столбец должен быть 70% а у второй последний?
В представлениях можно подключать стили CSS, через которые меняется отображение (колонка 70%).
У каждого представления может быть свои стили.
Если нужно, чтобы были некоторые общие стили и дополнительные, нужные только для этой страницы, используют что-то типа буферизирования вывода блока стилей.
<?php
class Assets
{
public static $cssLinks = array();
public static $css = array();
// добавить ссылку на файл стилей
public function addCssLink($link)
{
self::$cssLinks[$link] = $link;
}
// добавить блок стилей
public function addCss($name, $css) // $name - это чтобы не дублировались блоки стилей, на всякий случай
{
self::$css[$name] = $css;
}
public function renderCss()
{
foreach(self::$cssLinks as $url)
echo '<link href="'.$url.'" rel="stylesheet" type="text/css" />';
echo '<style type="text/css">';
foreach(self::$css as $css)
echo $css;
echo '</style>';
}
}
# index.php
$assets = new Assets();
// общие стили
$assets->addCssLink('https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css');
// ...
$view = new View();
$view->render('page.php', array(
'username' => $_SESSION['username']
));
# page.php
<?php
/**
* @var string $username
*/
$assets = new Assets();
$assets->addCssLink('/path/to/my/styles.css'); // ещё один файл стилей
$assets->addCss('h1 { color: red; }'); // или даже что-нибудь микроскопическое, только для этой страницы
?>
<html>
<head>
<?php $assets->renderCss() ?>
</head>
<body>
<h1>Hello, <?php echo $username ?></h1>
</body>
</html>
И аналогично про JS.