Как сделать шаблонизатор на php?

Придерживаюсь мнения что лучщий шаблонизатор для PHP это сайм PHP но есть вопрос: как его правильно реализовать.
Ну т.е. условно страница у нас отрабатывает и есть набор данных в аккаунте которые нужно показать пользователю:
title страницы, имя юзера, баланс, таблица с данными.
Вот где почитать как архитекрутно правильно это сделать?

Да понятно MVC, но... у меня нет контроллера. Запрос приходит на роутер, который определяет какой вызывать класс обработчик (это жирная модель по сути), класс всё готовит и должен выплюнуть результат в шаблон по идее. Передавать шаблонизатору он через массив вероятно всё будет, но как это лучше делать, как определить для этого набора данных можно использовать сущствующий шаблон или надо создавть новый? Как таблицы шаблонизаирова если в одной 3 столбца, в другой 5? Или 4 и 4, но у первой первый столбец должен быть 70% а у второй последний?
  • Вопрос задан
  • 3467 просмотров
Решения вопроса 2
copist
@copist
Empower people to give
Сначала - зачем нужен шаблонизатор. Тут подмена понятий. Скорее вопрос такой: как отделить вывод от заголовков. Если не отделять, то может быть такая ошибка

<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.
Ответ написан
hahenty
@hahenty
('•')
Преждевременная сверхархитектурная выдумка - на данном этапе лишняя и вредная головная боль.
Сейчас нужно написать некий прототип оформления данных. В нём будут самые простые условные операторы и циклы для выведения значений и их типов, причем в перемешку с хтмл. Когда этот прототип заработает как надо, тогда можно будет обдумать "архитектуру". И важно не затыкаться с улучшением какого-то отдельного условия, чтоб красиво - для дальнейших обдумываний будет проще разбирать однообразные конструкции с различающимися значениями, так виднее места для группировки.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 2
shindakioku
@shindakioku
Не сайтоклепатор
> Придерживаюсь мнения что лучщий шаблонизатор для PHP это сайм PHP но есть вопрос: как его правильно реализовать.
Зачем тогда еще один шаблонизатор?

> title страницы, имя юзера, баланс, таблица с данными.
Вот где почитать как архитекрутно правильно это сделать?
Шаблонизатор не этим занимается, он делает удобство использования php кода(брать данные), взгляните на twig, blade, например.

> Да понятно MVC, но... у меня нет контроллера.
Не обязательно.
Вы можете в модели возвращать что-то типа: return View::file($templatePath);

Передавать данные можно через тык
Ответ написан
@djay
Вот где почитать как архитекрутно правильно это сделать?


Нигде ты это не почитаешь. Рецепт уникален. Это тоже самое, что где можно почитать как создать свой фреймворк.

И да, говорят что жирные модели начинают от жира бесится, когда дело доходит до поиска мелких багов и тестирования.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы