Подключаем шаблонизатор 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)

  1. Семён Лобачевский 02 июня 2015, 10:59 # 0
    Такой вопрос: по текущей логике, у нас при каждом обращении к главной странице сайта, запускается контроллер Controllers_Home, в котором есть функция run(), а в ней обращение к шаблонизатору Fenom.
    Так вот это обращение идёт каждый раз и пересоздаётся папка с кэшем? или при каком-то условии, подгружается из кэша?
    1. Василий Наумкин 02 июня 2015, 11:02 # 0
      Нет, папка с кэшем только проверяется на существование, потому что без неё Fenom выбросит исключение и будет fatal error.

      Fenom сам кэширует скомпилированные шаблоны, и сам загружает их из своего кэша — у нас об этом голова вообще не болит.
      Хоть метод для очистки кэша мы и задали, но пока нигде не используем.
    2. Алексей 20 октября 2015, 16:26 # 0
      Интересно…
      Появилась ошибка на хостинге Nic.ru:
      Fatal error: Undefined constant 'T_ABSTRACT' in .../docs/Core/Fenom/Template.php on line 254
      На другом хостинге и на локалке всё нормально. Вроде как это вопрос версии php, но перебрал все от 5.2 до 5.6. Расширения тоже перепроверил на всякий случай.

      вздохнул… всё перепроверил, сделал заново — не помогло.
      Нет ли версий — что это может быть?
      1. Василий Наумкин 20 октября 2015, 19:35 # 0
        Нужно проверить, всё ли в порядке с модулем PHP Tokenizer — именно к нему идёт обращение на 254 строке.
        1. Алексей 20 октября 2015, 21:47 # 0
          Вот ведь… 2 раза включённые модули проверил.
          Модуль был выключен.
          Благодарю!
          1. Василий Наумкин 21 октября 2015, 03:16 # 0
            На здоровье!
      2. Николай Савин 03 января 2016, 10:41 # 0
        Столкнулся с ошибкой. В классе Home.php упорно не желали вызываться методы из переданного класса Core.php,
        Так как я новичок, и особенности ООП для меня пока не особо известны — ошибку искал долго.
        На Github нашел подсказку Василия — Опечатка в названии метода в файле Controller.php
        Оказывается Василий в предыдущих уроках опечатался и написал _construct вместо __construct.
        Помню заметил, но подумал «Ему виднее». В итоге метод не срабатывал и экземпляр класса core дальше не передавался.
        С другой стороны подобный поиск ошибки заставил меня прогнать весь код в голове заново.
        Я потренировался писать свои методы, посмотрел, что срабатывает, а что нет. Так что получилась дополнительная практика.

        1. Василий Наумкин 03 января 2016, 11:48 # 0
          Ты бы дал ссылку на очепятку — я бы поправил.
          1. Николай Савин 03 января 2016, 12:21 # 0
            Ссылка
            После этой строки
            Давайте создадим новую директорию /core/controllers/ и добавим в неё класс Home.php:
            В классе метод __construct с очепяткой назван (не хватает _ )
            1. Василий Наумкин 03 января 2016, 12:25 # 0
              Спасибо, поправил!
        2. Александр 29 января 2016, 12:22 # 0
          Василий, не подскажешь где почитать доступно о такой форме записи
          Fenom::registerAutoload();
          почему именно так, а не, условно,
          $fenom->registerAutoload();
          как мы раньше вызывали методы своих классов?
          На php.net почитал, но ничего толком не понял. Спасибо.
          1. Александр 29 января 2016, 12:45 # 0
            Почитав stackoverflow.com и php.net подробнее, сформулировал ответ так:
            Такой записью мы вызываем статический элемент без создания экземпляра класса
            Пока только непонятно, почему метод registerAutoload создан именно статическим, но думаю ответ появится в процессе дальнейшего чтения документации по этой теме.
            1. Василий Наумкин 29 января 2016, 13:01 # 0
              Потому что для загрузки файлов не нужно инициализировать весь класс.

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