Подключаем шаблонизатор Fenom

Знаю-знаю, мы собирались работать на чистом PHP, безо всяких фреймворков, но о шаблонизаторах речи не было!
Если серьёзно, то я по всякому прикинул, как заставить наш простенький сайт выводить HTML, и не вписывать его в PHP, а хранить в шаблонах.
Варианта ровно два: написать собственный жуткий глючный велосипед, который будет читать шаблон и заменять в нём плейсхолдеры на значения через str_replace(), или подключить нормальный шаблонизатор.
Так как мы все тут от MODX люди не очень далёкие, так почему бы не освоить работу с Fenom, который в нём с некоторых пор доступен? Думаю, возражений не будет, так что поехали!

Зачем?

Для начала давайте определимся, зачем нам вообще нужен шаблонизатор? Можно же писать HTML прямо в PHP и выводить его через echo?
Можно, конечно. Но такой код будет очень сложно развивать и поддерживать, даже если вы работаете над ним в одиночку. А если с вами будет работать еще и дизайнер\верстальщик, то ему придётся выучить PHP и разобраться в вашем коде, чтобы вносить изменения.
Подключение же шаблонизатора позволит очень просто менять внешний вид сайта, используя его синтаксис. Знания PHP не нужны, а код и представление разделены.

Подключение

Распаковываем архив и переносим всё из директории scr в нашу Core. Так как Fenom придерживается PSR, то свою директорию называет с большой буквы. Чтобы не ломать единообразие, переименуем и нашу директорию controllers в Controllers.
Заодно порадуемся тому факту, что путь к контроллерам у нас задан в конфиге класса Core под ключом controllersPath - указываем заглавную букву и там. Код на GitHub сейчас такой.
Теперь нам нужно как-то добавить работу с Fenom в наш класс Core. Пишем для этого отдельный метод getFenom():

    public function getFenom() {
        // Работаем только, если переменная класса пуста
        if (!$this->fenom) {
            // Пробуем загрузить шаблонизатор
            // Все выброшенные исключения внутри этого блока будут пойманы в следующем
            try {
                // Подключаем класс загрузки
                if (!class_exists('Fenom')) {
                    require 'Fenom.php';
                    // Регистрируем остальные классы его методом
                    Fenom::registerAutoload();
                }
                // Проверяем и создаём директорию для кэширования скомпилированных шаблонов
                if (!file_exists($this->config['cachePath'])) {
                    mkdir($this->config['cachePath']);
                }
                // Запускаем Fenom
                $this->fenom = Fenom::factory($this->config['templatesPath'], $this->config['cachePath'], $this->config['fenomOptions']);
            }
            // Ловим исключения, если есть, и отправляем их в лог
            catch (Exception $e) {
                $this->log($e->getMessage());
                // Возвращаем false
                return false;
            }
        }

        // Возвращаем объект Fenom
        return $this->fenom;
    }
Как видите, загрузка Fenom происходит только один раз, потом Core будет отдавать уже инициализированный экземпляр. Также по коду видно, что добавились и новые параметры в настройки, и метод log(), для вывода ошибок.
Предлагаю всё это посмотреть вам на GitHub, вместе с нашим первым шаблоном в /Core/Templates/home.tpl:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Третий курс обучения на bezumkin.ru</title>
</head>
<body>
    <h1>Привет, мир!</h1>
</body>
</html>
Который выводит контроллер Home:

    public function run() {
        // Метод getFenom() может вернуть или false, или объект
        // Так что нужно проверять, что именно приходит
        if ($fenom = $this->core->getFenom()) {
            return $fenom->fetch('home.tpl');
        }
        else {
            return '';
        }
    }
Скелет шаблонизации готов, можно работать дальше.

Шаблоны

Предлагаю теперь нам еще подключить Bootstrap для оформления страниц и нарисовать простейший шаблон, которым будут оформлены главная страница, и test.
Качаем последний Bootstrap, распаковывем и кладём в директорию /assets/. Вот состояние нашего репозитория с подключенным Bootstrap.
Дальше предусматриваем в шаблоне переменные Fenom, которые он потом заменит на полученные от контроллера значения:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{$pagetitle}</title>
    <link rel="stylesheet" href="/assets/css/bootstrap.min.css">
</head>
<body>
    <div class="container">
        <h3>{$longtitle ?: $pagetitle}</h3>
        {$content}
    </div>
</body>
<footer>
    <script src="/assets/js/jquery-2.1.4.min.js"></script>
    <script src="/assets/js/bootstrap.min.js"></script>
</footer>
</html>
После чего в контроллере Home меняем метод run() вот так:

    public function run() {
        if ($fenom = $this->core->getFenom()) {
            return $fenom->fetch('home.tpl', array(
                'pagetitle' => 'Тестовый сайт',
                'longtitle' => 'Третий курс обучения',
                'content' => 'Текст главной страницы курса обучения на bezumkin.ru',
            ));
        }
        else {
            return '';
        }
    }
Как видите, наш шаблон уже умеет проверять longtitle на пустоту и подставлять вместо него pagetitle, если нужно. Пользуемся этим в методе run() контроллера Test:

    public function run() {
        if ($fenom = $this->core->getFenom()) {
            return $fenom->fetch('home.tpl', array(
                'pagetitle' => 'Тестовая страница',
                'longtitle' => '',
                'content' => 'Текст тестовой страницы курса обучения на bezumkin.ru',
            ));
        }
        else {
            return '';
        }
    }
Вот мы и немного оформили наши две страницы с помощью шаблонов Fenom и Twitter Bootstrap.

Заключение

На следующем уроке мы сильнее погрузимся в шаблонизацию и научимся работать с синтаксисом Fenom.
Попробуем вывести панель навигации по сайту, написать отдельный шаблон для страницы Test и выделить общие элементы двух шаблонов в отдельный файл, чтобы не копировать один код (head, footer).
На данный момент наш сайт выглядит вот так.

13 комментариев

Семён Лобачевский
Такой вопрос: по текущей логике, у нас при каждом обращении к главной странице сайта, запускается контроллер Controllers_Home, в котором есть функция run(), а в ней обращение к шаблонизатору Fenom. Так вот это обращение идёт каждый раз и пересоздаётся папка с кэшем? или при каком-то условии, подгружается из кэша?
Василий Наумкин
Нет, папка с кэшем только проверяется на существование, потому что без неё Fenom выбросит исключение и будет fatal error.
Fenom сам кэширует скомпилированные шаблоны, и сам загружает их из своего кэша - у нас об этом голова вообще не болит. Хоть метод для очистки кэша мы и задали, но пока нигде не используем.
Алексей
Интересно... Появилась ошибка на хостинге Nic.ru: Fatal error: Undefined constant 'T_ABSTRACT' in .../docs/Core/Fenom/Template.php on line 254 На другом хостинге и на локалке всё нормально. Вроде как это вопрос версии php, но перебрал все от 5.2 до 5.6. Расширения тоже перепроверил на всякий случай.
вздохнул... всё перепроверил, сделал заново - не помогло. Нет ли версий - что это может быть?
Василий Наумкин
Нужно проверить, всё ли в порядке с модулем PHP Tokenizer - именно к нему идёт обращение на 254 строке.
Алексей
Вот ведь... 2 раза включённые модули проверил. Модуль был выключен. Благодарю!
Василий Наумкин
На здоровье!
Николай Савин
Столкнулся с ошибкой. В классе Home.php упорно не желали вызываться методы из переданного класса Core.php, Так как я новичок, и особенности ООП для меня пока не особо известны - ошибку искал долго. На Github нашел подсказку Василия - Опечатка в названии метода в файле Controller.php Оказывается Василий в предыдущих уроках опечатался и написал _construct вместо __construct. Помню заметил, но подумал "Ему виднее". В итоге метод не срабатывал и экземпляр класса core дальше не передавался. С другой стороны подобный поиск ошибки заставил меня прогнать весь код в голове заново. Я потренировался писать свои методы, посмотрел, что срабатывает, а что нет. Так что получилась дополнительная практика.
Василий Наумкин
Ты бы дал ссылку на очепятку - я бы поправил.
Николай Савин
СсылкаПосле этой строки > Давайте создадим новую директорию /core/controllers/ и добавим в неё класс Home.php:
В классе метод __construct с очепяткой назван (не хватает _ )
Василий Наумкин
Спасибо, поправил!
Александр
Василий, не подскажешь где почитать доступно о такой форме записи
Fenom::registerAutoload();
почему именно так, а не, условно,
$fenom->registerAutoload();
как мы раньше вызывали методы своих классов? На php.net почитал, но ничего толком не понял. Спасибо.
Александр
Почитав stackoverflow.com и php.net подробнее, сформулировал ответ так: Такой записью мы вызываем статический элемент без создания экземпляра класса Пока только непонятно, почему метод registerAutoload создан именно статическим, но думаю ответ появится в процессе дальнейшего чтения документации по этой теме.
Василий Наумкин
Потому что для загрузки файлов не нужно инициализировать весь класс.
Статические методы - это, грубо говоря, обычные функции, которым не нужно обращаться к другим методам или свойствам самого класса. Вернее, обращаться они могут только к таким же статическим методам.
bezumkin.ru
Личный сайт Василия Наумкина
Прямой эфир
Василий Наумкин
03.12.2024, 13:13:34
Генерация - это создание статичный файлов, для их работы потом pm2 не нужен, только правильная настр...
Василий Наумкин
22.11.2024, 03:33:54
Спасибо!
inna
06.11.2024, 15:47:13
Да. Все работает. Спасибо.
Василий Наумкин
01.07.2024, 11:56:41
Да, верно, именно так. А в контроллере, скорее всего, ловить данные методом post.
Василий Наумкин
26.06.2024, 09:38:15
О, точно, вылезает если не залогинен. Спасибо, исправил!
Василий Наумкин
09.04.2024, 04:45:01
> Ошибка 500 Это не похоже на ошибку Nginx, это скорее всего ошибка PHP - надо смотреть его логи. ...
Василий Наумкин
20.03.2024, 21:21:52
Volledig!
Андрей
14.03.2024, 13:47:10
Василий! Как всегда очень круто! Моё почтение!
russel gal
09.03.2024, 20:17:18
> А этот стоило написать хотя бы затем, чтобы получить комментарий от юзера, который ничего не писал...
Александр Наумов
27.01.2024, 03:06:18
Василий, спасибо! Извини, тупанул.