Расширение и наследование шаблонов Fenom
На прошлом занятии мы подключили шаблонизатор Fenom к нашей системе и написали простенький шаблон.
Теперь пришло время написать уже нормальные шаблоны для страниц Home и Test, при этом они будут наследовать один общий шаблон Base, в котором будет генерироваться меню сайта.
В принципе, основная часть сайта после этого будет закончена и нужно будет только наращивать функционал - писать тексты, выводить их в шаблонах с разбивкой на страницы и прочая, привычная по MODX работа.
Поэтому, в конце урока вам предлагается выбрать, как именно мы будем работать дальше. На всякий случай, вот как должен выглядеть наш сайт после этого урока - http://s1889.bez.modhost.pro.
Рендер шаблонов
Для начала, давайте выделим общий метод обработки шаблонов. Всё же, неправильно, когда каждый контроллер вызывает делает это по-своему.
Пускай будет один метод template в Controller, а при необходимости дочерние контроллеры всё равно смогут его расширить:
/**
* Шаблонизация
*
* @param string $tpl Имя шаблона
* @param array $data Массив данных для подстановки
* @param Controller|null $controller Контроллер для передачи в шаблон
*
* @return mixed|string
*/
public function template($tpl, array $data = array(), $controller = null) {
$output = '';
if (!preg_match('#\.tpl$#', $tpl)) {
$tpl .= '.tpl';
}
if ($fenom = $this->core->getFenom()) {
try {
$data['_core'] = $this->core;
$data['_controller'] = !empty($controller) && $controller instanceof Controller
? $controller
: $this;
$output = $fenom->fetch($tpl, $data);
}
catch (Exception $e) {
$this->core->log($e->getMessage());
}
}
return $output;
}
Думаю, по предыдущим урокам уже понятно, что здесь происходит: мы пытаемся оформить данные указанным шаблоном, а если этот процесс выбросит какое-то исключение, то мы запишем его в лог и вернём пустоту.
Нужно обратить внимание, что в этом методы мы передаём в массив данных наш объект Сore в переменную {$_core} - таким образом, шаблон сможет выполнять любые методы основного класса и получать системные настройки. Для тех же целей рядышком и объект {$_controller}, который мы можем передать из дочернего контроллера. А если не передали - то там будет базовый контроллер.
Таким образом, в любом шаблоне мы сразу получаем ссылку на ядро и контроллер со всеми их публичными методами и свойствами. Вот зачем мы изначально определяли эти public, protected и private - чтобы шаблон не мог использовать всё подряд.
Теперь метод run() нашего дочернего контроллера Home выглядит вот так:
public function run() {
return $this->template('home', array(
'pagetitle' => 'Тестовый сайт',
'longtitle' => 'Третий курс обучения',
'content' => 'Текст главной страницы курса обучения на bezumkin.ru',
), $this);
}
Как видите, последним параметром мы передаём ссылку на сам контроллер Home. Выгружаем наши изменения на GitHub для истории.
Расширение шаблонов
Ну вот и пришло время нам написать базовый шаблон и дочерние, которые будут его расширять. Логика очень похожа на PHP, только с поправкой на синтаксис Fenom.
Те шаблоны, которые мы не будем вызывать напрямую, а будем только использовать в других шаблонах, сразу предлагаю называть с подчёркивания. Вот наш шаблон /core/Templates/_base.tpl:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>
{block 'title'}Тестовые уроки на bezumkin.ru{/block}
</title>
{block 'css'}
<link rel="stylesheet" href="/assets/css/bootstrap.min.css">
{/block}
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-10">
{block 'content'}
{if $longtitle != ''}
<h3>{$longtitle}</h3>
{elseif $pagetitle != ''}
<h3>{$pagetitle}</h3>
{/if}
{$content}
{/block}
</div>
<div class="col-md-2">
{block 'sidebar'}
Сайдбар
{/block}
</div>
</div>
</div>
</body>
<footer>
{block 'js'}
<script src="/assets/js/jquery-2.1.4.min.js"></script>
<script src="/assets/js/bootstrap.min.js"></script>
{/block}
</footer>
</html>
Все части шаблона, которые можно будет переопределить в дочерних, мы оборачиваем в {block 'имя'}. Так получаются блоки: - title - заголовок страницы
- content - содержимое
- sidebar - сайдбар справа, пока пустой
- css - подключаемые стили css
- js - подключаемые скрипты
В этих блоках у нас прописаны теги по-умолчанию, и теперь мы можем расширить их из дочерних шаблонов. Для этого в шаблоне home пишем следующее:
{extends '_base.tpl'}
{block 'content'}
<div class="jumbotron">
{parent}
</div>
{/block}
Первая строка указывает на шаблон, который мы расширяем. Дальше мы переопределяем блок content, помещая всё стандартное содержимое в div с классом jumbotron. Специальный тег {parent} указывает, что мы используем именно содержимое родительского блока, а в нём у нас прописана вставка {$pagetitle} и {$content}.
Повторю логику еще раз: 1. Контроллер Home использует шаблон home
- Тот расширяет шаблон _base
- При расширении заменяется блок content
- А внутри замены вызывается {parent}, что вставляет содержимое по-умолчанию в расширяющий блок
В итоге, во время компиляции, в шаблоне home выходит примерно вот так:
{block 'content'}
<div class="jumbotron">
{if $longtitle != ''}
<h3>{$longtitle}</h3>
{elseif $pagetitle != ''}
<h3>{$pagetitle}</h3>
{/if}
{$content}
</div>
{/block}
Мы расширили шаблон _base, используя значение родителя по-умолчанию.
А вот шаблон test пускай просто заменит тег title, а всё остальное оставит как есть:
{extends '_base.tpl'}
{block 'title'}
{$title} / {parent}
{/block}
Здесь мы добавляем свой заголовок страницы к стандартному, через косую.
Чтобы шаблоны не ругались на отсутствующие переменные (например, на longtitle) и не писали на страницу E_NOTICE, я еще указал в настройках класса Core параметр force_verify для Fenom:
'fenomOptions' => array(
'auto_reload' => true,
'force_verify' => true,
),
Всё, что у нас получилось, можно смотреть по ссылкам: - http://s1889.bez.modhost.pro/
А вот все изменения на GitHub.
Наследование шаблонов
Мы разобрали механизм расширения, а теперь я предлагаю познакомиться с механизмом наследования (включения одного шаблона в другой).
Давайте добавим уже на сайт навигационную панель Bootstrap с пунктами меню. Пишем чанк /core/Templates/_navbar.tpl:
<nav class="navbar navbar-default">
<div class="navbar-header">
<a class="navbar-brand" href="/">Course 3</a>
</div>
<ul class="nav navbar-nav">
{set $pages = $_controller->getMenu()}
{foreach $pages as $name => $page}
{if $_controller->name == $name}
<li class="active">
<a href="#" style="cursor: default;" onclick="return false;">{$page.title}</a>
</li>
{else}
<li><a href="{$page.link}">{$page.title}</a></li>
{/if}
{/foreach}
</ul>
</nav>
Понятное дело, здесь нас интересует код генерации пунктов меню:
{set $pages = $_controller->getMenu()}
{foreach $pages as $name => $page}
{if $_controller->name == $name}
<li class="active">
<a href="#" style="cursor: default;" onclick="return false;">{$page.title}</a>
</li>
{else}
<li><a href="{$page.link}">{$page.title}</a></li>
{/if}
{/foreach}
Первым делом мы вызываем из контроллера метод getMenu(), вот он:
/**
* Возвращает пункты меню сайта
*
* @return array
*/
public function getMenu() {
return array(
'home' => array(
'title' => 'Главная',
'link' => '/',
),
'test' => array(
'title' => 'Тестовая',
'link' => '/test/',
)
);
}
Особо не умничая, пока что, просто перечисляем наши страницы.
Дальше в шаблоне идёт прокрутка массива и определение текущей страницы. Для этого я добавил в контроллеры публичное свойство name - чтобы отличать их друг от друга.
Проверяя {$_controller->name} всегда можно понять, на какой страницы мы находимся и отметить этот пункт в панели активным.
Осталось только подключить вывод navbar в шаблоне _base:
{block 'navbar'}
{include '_navbar.tpl'}
{/block}
Оборачивание в блок navbar позволяет нам, если что, переопределить навигационную панель из дочернего шаблона.
Заключение
Итоговый коммит с результатами сегодняшней работы - вот здесь. Как видите, основной движок сайта уже написан.
Дальше мы можем создавать новые шаблоны, включать их друг в друга, расширять, прописывать новые методы в ядре и контроллерах и т.д.
Теперь нужно определиться, в какую сторону мы дальше пойдём: 1. Продолжаем работу на файлах, без БД
- Работаем с БД через PDO, "сырыми" SQL запросами
- Устанавливаем фреймворк xPDO, пишем схему таблиц, генерируем модель и строим запросы с его помощью
Пока что на курсах всего 3 человека, поэтому предлагаю вам самим решить, какие дальше будут уроки.
0
👍
👎
❤️
🔥
😮
😢
😀
😡
3 970
05.06.2015, 11:05:00
12 комментариев
Семён Лобачевский
06.06.2015, 16:41:38
Мне было бы интересно узнать про третий вариант: - Устанавливаем фреймворк xPDO, пишем схему таблиц, генерируем модель и строим запросы с его помощью
Василий Наумкин
06.06.2015, 16:50:54
Да, мне он тоже больше нравится.
Если никто не будет против - то дальше работаем с ним.
Перетягин Илья
06.06.2015, 18:14:53
Мы таким макаром придем от чистого пхп к движкам. Я в целом не против, но не и не поддерживаю.
Василий Наумкин
06.06.2015, 18:22:04
Выходит именно так: или продолжать велосипедить, или использовать то, что уже придумали другие.
В реальной работе ты же не будешь писать всё сам - зачем? На данный момент мы уже написали ядро сайта на чистом PHP, которое вполне понятно и прозрачно использует сторонний шаблонизатор.
Поэтому, давайте решать сейчас, как будем продолжать занятия. Я вот не знаю даже, что еще писать на чистом PHP. У меня все дороги ведут в Composer и загрузку готовых решений - это и проще и удобнее.
Ты если хочешь, чтобы я еще про что-то отдельно написал - просто скажи, про что именно.
Перетягин Илья
06.06.2015, 18:33:24
Я еще не могу добраться до второго урока, так как времени нету, по этому даже и не знаю, что нужно. Если ты говоришь, что больше писать нечего, значит так и есть.
Василий Наумкин
06.06.2015, 18:40:08
Ясно. Ну, когда доберёшься, думаю, вопросов не будет.
А если будут, то я тебе персонально всё расскажу =) Вопросы можно будет задавать еще месяца 3, а то и больше, после окончания курса.
Перетягин Илья
06.06.2015, 18:42:32
Хорошо, спасибо большое!
Максим Степанов
08.11.2015, 14:10:17
Здравствуйте Василий. Подскажите а как расширяя базовый шаблон можно убрать sidebar и оставить только content ?
Василий Наумкин
08.11.2015, 14:18:55
В моём примере его нельзя убрать, можно только оставить пустым.
Но можно добавить еще один блок в основном шаблоне и заменять уже его:
Переопределяем блок container
Максим Степанов
08.11.2015, 14:24:55
Понял, спасибо
Николай Савин
04.01.2016, 12:37:53
В начале урока в первом блоке кода класса Controller появилось несколько вопросов. Что за конструкция try catch - ладно загуглил, что такое instanceof тоже суть разобрал и понял. Остался только чисто теоретический вопрос по логике работы instanceof
Данный оператор (instanceof) проверяет, является ли переданный объект $controler экземпляром класса Controller. Верно? Вроде так. Но мы передаем сюда объект Controllers_Home, к примеру. И выражение все равно остается верным, instanceof возвращает TRUE (или что там возвращается?). То есть Controllers_Home является экземпляром класса Controller? Это результат того, что класс Controllers_Home расширяет класс Controller?
Василий Наумкин
04.01.2016, 12:44:50
Да, абсолютно всё, что ты написал - верно.
bezumkin.ru
Личный сайт Василия Наумкина
Прямой эфир
Василий Наумкин
01.07.2024, 11:56:41
Да, верно, именно так.
А в контроллере, скорее всего, ловить данные методом post.
Василий Наумкин
26.06.2024, 09:38:15
О, точно, вылезает если не залогинен.
Спасибо, исправил!
Василий Наумкин
09.04.2024, 04:45:01
> Ошибка 500
Это не похоже на ошибку Nginx, это скорее всего ошибка PHP - надо смотреть его логи.
...
russel gal
09.03.2024, 20:17:18
> А этот стоило написать хотя бы затем, чтобы получить комментарий от юзера, который ничего не писал...
Александр Наумов
27.01.2024, 03:06:18
Василий, спасибо!
Извини, тупанул.