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

Знаю-знаю, мы собирались работать на чистом PHP, безо всяких фреймворков, но о шаблонизаторах речи не было!

Если серьёзно, то я по всякому прикинул, как заставить наш простенький сайт выводить HTML, и не вписывать его в PHP, а хранить в шаблонах.

Варианта ровно два: написать собственный жуткий глючный велосипед, который будет читать шаблон и заменять в нём плейсхолдеры на значения через str_replace(), или подключить нормальный шаблонизатор.

Так как мы все тут от MODX люди не очень далёкие, так почему бы не освоить работу с Fenom, который в нём с некоторых пор доступен? Думаю, возражений не будет, так что поехали!

Зачем?

Для начала давайте определимся, зачем нам вообще нужен шаблонизатор? Можно же писать HTML прямо в PHP и выводить его через echo?

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

Подключение же шаблонизатора позволит очень просто менять внешний вид сайта, используя его синтаксис. Знания PHP не нужны, а код и представление разделены.

Подключение

Идём в репозиторий Fenom и скачиваем свежий релиз.

Распаковываем архив и переносим всё из директории 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).

На данный момент наш сайт выглядит вот так.

← Предыдущая заметка
Базовый контроллер и его методы
Следующая заметка →
Расширение и наследование шаблонов Fenom
Комментарии (13)
Семён Лобачевский
02.06.2015 07:59

Такой вопрос: по текущей логике, у нас при каждом обращении к главной странице сайта, запускается контроллер Controllers_Home, в котором есть функция run(), а в ней обращение к шаблонизатору Fenom. Так вот это обращение идёт каждый раз и пересоздаётся папка с кэшем? или при каком-то условии, подгружается из кэша?

bezumkinВасилий Наумкин
02.06.2015 08:02

Нет, папка с кэшем только проверяется на существование, потому что без неё Fenom выбросит исключение и будет fatal error.

Fenom сам кэширует скомпилированные шаблоны, и сам загружает их из своего кэша - у нас об этом голова вообще не болит. Хоть метод для очистки кэша мы и задали, но пока нигде не используем.

Алексей
20.10.2015 13:26

Интересно... Появилась ошибка на хостинге Nic.ru: Fatal error: Undefined constant 'T_ABSTRACT' in .../docs/Core/Fenom/Template.php on line 254 На другом хостинге и на локалке всё нормально. Вроде как это вопрос версии php, но перебрал все от 5.2 до 5.6. Расширения тоже перепроверил на всякий случай.

вздохнул... всё перепроверил, сделал заново - не помогло. Нет ли версий - что это может быть?

bezumkinВасилий Наумкин
20.10.2015 16:35

Нужно проверить, всё ли в порядке с модулем PHP Tokenizer - именно к нему идёт обращение на 254 строке.

Алексей
20.10.2015 18:47

Вот ведь... 2 раза включённые модули проверил. Модуль был выключен. Благодарю!

bezumkinВасилий Наумкин
21.10.2015 00:16

На здоровье!

biz87Николай Савин
03.01.2016 07:41

Столкнулся с ошибкой. В классе Home.php упорно не желали вызываться методы из переданного класса Core.php, Так как я новичок, и особенности ООП для меня пока не особо известны - ошибку искал долго. На Github нашел подсказку Василия - Опечатка в названии метода в файле Controller.php Оказывается Василий в предыдущих уроках опечатался и написал _construct вместо __construct. Помню заметил, но подумал "Ему виднее". В итоге метод не срабатывал и экземпляр класса core дальше не передавался. С другой стороны подобный поиск ошибки заставил меня прогнать весь код в голове заново. Я потренировался писать свои методы, посмотрел, что срабатывает, а что нет. Так что получилась дополнительная практика.

bezumkinВасилий Наумкин
03.01.2016 08:48

Ты бы дал ссылку на очепятку - я бы поправил.

biz87Николай Савин
03.01.2016 09:21

СсылкаПосле этой строки > Давайте создадим новую директорию /core/controllers/ и добавим в неё класс Home.php:

В классе метод __construct с очепяткой назван (не хватает _ )

bezumkinВасилий Наумкин
03.01.2016 09:25

Спасибо, поправил!

Александр
29.01.2016 09:22

Василий, не подскажешь где почитать доступно о такой форме записи

Fenom::registerAutoload();

почему именно так, а не, условно,

$fenom->registerAutoload();

как мы раньше вызывали методы своих классов? На php.net почитал, но ничего толком не понял. Спасибо.

Александр
29.01.2016 09:45

Почитав stackoverflow.com и php.net подробнее, сформулировал ответ так: Такой записью мы вызываем статический элемент без создания экземпляра класса Пока только непонятно, почему метод registerAutoload создан именно статическим, но думаю ответ появится в процессе дальнейшего чтения документации по этой теме.

bezumkinВасилий Наумкин
29.01.2016 10:01

Потому что для загрузки файлов не нужно инициализировать весь класс.

Статические методы - это, грубо говоря, обычные функции, которым не нужно обращаться к другим методам или свойствам самого класса. Вернее, обращаться они могут только к таким же статическим методам.

bezumkin
Василий Наумкин
04.07.2022 23:34
Что-то странное у тебя произошло: миграция есть, и вроде как выполнена, но таблицы при этом отсутств...
inetlover
Александр Наумов
03.07.2022 20:36
Василий, спасибо! Все понятно!
bezumkin
Василий Наумкин
02.07.2022 20:28
Спасибо, поправил!
bezumkin
Василий Наумкин
30.06.2022 03:58
Есть ли возможность формировать &quot;friendly URL aliases&quot;, используя аналог translit MODx? ...
bezumkin
Василий Наумкин
27.06.2022 03:32
Спасибо за исправления, очень выручаешь =) Но учитывая количество не описаных в заметке дополнительн...
bezumkin
Василий Наумкин
27.06.2022 03:10
что будет использоваться для вывода многоуровневого меню Посмотри как работают комментарии на этом ...
bezumkin
Василий Наумкин
25.06.2022 11:56
Поправил, спасибо!
bezumkin
Василий Наумкин
21.06.2022 01:58
onLoad(data) { this.total = data.total }, и onLoad({total}) { this.total = total }, В нашем случ...
bezumkin
Василий Наумкин
20.06.2022 14:01
Прекрасно тебя понимаю, я когда сам в этом разбирался - голова дымилась. Но зато теперь прямо-таки п...
bezumkin
Василий Наумкин
20.06.2022 09:30
Не надо, оно по умолчанию так - я просто чуть более подробно написал.