Сначала - зачем нужен шаблонизатор. Тут подмена понятий. Скорее вопрос такой: как отделить вывод от заголовков. Если не отделять, то может быть такая ошибка
<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.