37
IMP Templates Простая функциональная шаблонизация в PHP Interlabs 2 октября 2014 1 / 37

Простая функциональная шаблонизация в PHP

  • View
    427

  • Download
    1

Embed Size (px)

DESCRIPTION

Очередной семинар посвящен механизму шаблонизации, используемому в новом PHP-фреймворке.

Citation preview

Page 1: Простая функциональная шаблонизация в PHP

IMP TemplatesПростая функциональная шаблонизация в PHP

Interlabs

2 октября 2014

1 / 37

Page 2: Простая функциональная шаблонизация в PHP

О чем речь

• о разных подходах к шаблонизации• о шаблонизации в новом фреймворке• о структурировании шаблонов• о роли верстальщика в разработке сайта

2 / 37

Page 3: Простая функциональная шаблонизация в PHP

Шаблонизация в PHPPHP PHP-файлы + include() и разнообразные

вариации на эту тему

vs

Handlebars клон известного шаблонизатора JavaScript,теоретически можно унифицировать шаблоны сJavaScript, на практике — маловероятно

Twig клон питоновского шаблонизатора, стандартSymfony, «давайте сделаем все красиво»

. . . еще много вариантов «более лучший язык шаблонизации»3 / 37

Page 4: Простая функциональная шаблонизация в PHP

Языки шаблонизации«Верстальщику достаточно знать язык шаблонов».

Цена вопроса:

• объемная реализация (Twig > 7.5 CLOC)• необходимость отдельной стадии компиляции шаблона• как следствие необходимость кеширования шаблона• ограничения языка описания шаблона• сложный API для написания расширений

Но ведь PHP уже язык шаблонизации?4 / 37

Page 5: Простая функциональная шаблонизация в PHP

PHP как шаблонизатор

Неэстетично. . .

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

• можно писать логику приложения непосредственно вшаблоне (но не нужно)

. . . зато дешево, надежно и практично.5 / 37

Page 6: Простая функциональная шаблонизация в PHP

Шаблоны в новом фреймворкеПочему не Twig и что мы вообще хотим:

• простое в реализации решение• без промежуточной фазы парсинга шаблонов,обязательного кеширования и т.д.

• PHP уже шаблонизатор, еще один язык не нужен• нужна возможность структурирования шаблонов (блоки,наследование, вызов)

• необходима изоляция данных времени выполнения междушаблонами

imp\text\templateфункциональные (типа) шаблоны

6 / 37

Page 7: Простая функциональная шаблонизация в PHP

imp\text\template• базовая реализация — около 300 строк• функциональные шаблоны: каждый шаблон — замыкание• определение и выполнение шаблона разделены• код шаблона — PHP, свой парсинг и компиляция не нужны• данные периода выполнения разделены• наследование, блочная структура, переопределение• с точки зрения структурирования напоминает Twig

<?php $this->is(function (array $args, $yield) { ?><p>Hello, <?= $args[’name’] ?></p><?php }) ?>

7 / 37

Page 8: Простая функциональная шаблонизация в PHP

API// Определение шаблона:

$this->is(function (array $args, $yield) { ... } )$this->is($parentTemplate, function (array $args, $yield) { ... } )$this->in($areaName, function (array $args, $yield) { ... } )$this->flags($type, array $flags)$this->defaults(array $defaultArgs);

// Тело шаблона:

$this->area($area, function (array $args, $yield) { ... } )$this->getFlags($type);

// Вызов шаблона:

$bundle->call($template, array $args);$template(array $args);$template->render(array $args);

8 / 37

Page 9: Простая функциональная шаблонизация в PHP

TemplateBundle• каждый шаблон — отдельный файл в каталоге шаблонов• имя шаблона соответствует пути в этом каталоге• несколько каталогов шаблонов можно объединять —используется первый найденный

• все шаблоны приложения — TemplateBundle

use imp\text\template\TemplateBundle;

$bundle = new TemplateBundle([’tpl/theme’, // - тема оформления’tpl/default’ // - шаблоны по умолчанию

]);

9 / 37

Page 10: Простая функциональная шаблонизация в PHP

Загрузка шаблона

$front = $bundle[’site.page.front’];$form = $bundle[’site.form’];$menu = $bundle[’site.menu’];

tpl/theme/site/page/

front.phtml <- site.page.frontform.phtml <- site.form

tpl/default/site/form.phtmlmenu.phtml <- site.menu

10 / 37

Page 11: Простая функциональная шаблонизация в PHP

Template• объект класса imp\text\template\Template• создается и кешируется экземпляром TemplateBundle• загружает один или несколько файлов определения• выполняемый объект, выполнение формирует результат• данные передаются при выполнении• загружается только один раз• может выполняться многократно с различными данными

$page = $templates[’site.page.front’];$page([ ’page.title’ => ’Test page’ ]);$page([ ’page.title’ => ’Another page’ ]);

11 / 37

Page 12: Простая функциональная шаблонизация в PHP

Определение шаблона// test.phtml<?php $this->is(function (array $args, $yield) { ?><p>Hello, <?= $args[’name’] ?></p>

<?php }) ?>

• выполняется в контексте объекта шаблона• is() определяет содержимое шаблона• любой вывод за пределами is() игнорируется• $args — данные шаблона при его вызове• $yield используется при наследовании шаблонов• внутри замыкания — разметка, возможно, дополнительныепеременные, стараемся минимизировать код

12 / 37

Page 13: Простая функциональная шаблонизация в PHP

Контекст определенияВ момент определения доступны переменные, которые можнопередать в замыкание:

• по умолчанию: template (он же $this), $bundle• дополнительно — набор пользовательских переменных

$bundle = new TemplateBundle($path, [’app’ => $application, // - пользовательский контекст

]);

// template.phtml

<?php $this->is(function ($args, $yields) use ($bundle, $app) { ?>...<?php }) ?>

13 / 37

Page 14: Простая функциональная шаблонизация в PHP

Наследование шаблонаБазовый шаблон вызывает производный в виде $yield:

// page.phtml — базовый шаблон, произвольная страница<?php $this->is(function ($args, $yield) { ?><html><body>

<div class="head">...</div><?php $yield($args); ?><div class="foot">...</div>

</body></html><?php }) ?>

// front.phtml — производный шаблон, главная страница<?php $this->is(’page’, function ($args, $yield) { ?><div class="showcase">..</div><div class="news">...</div><?php }) ?>

14 / 37

Page 15: Простая функциональная шаблонизация в PHP

Наследование шаблона

• устанавливается передачей в качестве первого аргументаis() имени родительского шаблона

• родительский шаблон должен вызывать $yield() внужном месте

• загружаются несколько определений, но объект шаблонавсе равно один

• поэтому отдельно загруженный родительский шаблон —отдельный шаблон

Выполнение сверху вниз, отродительского к производному

15 / 37

Page 16: Простая функциональная шаблонизация в PHP

Области выводаОпределение в базовом шаблоне, заполнение в производных.

// родительский шаблон html.phtml<?php $this->is(function ($args, $yield) { ?>

<html><head><script>

<?php $this->area(’script’, $args) ?></script>

</head><body><?php $yield($args) ?></body></html>

<?php }) ?>

// производный шаблон news.phtml<?php $this->is(function (array $args, $yield) { ?>

<h1>News...</h1><?php }) ?><?php $this->in(’script’, function (array $args, $yield) { ?>

require([’site/news’], function (news) { ... });<?php }) ?>

16 / 37

Page 17: Простая функциональная шаблонизация в PHP

Области вывода<?php $this->area(’sidebar’, $args) ?>

<?php $this->in(’sidebar’, function (array $args, $yield) { ?><p>Перед содержимым области родительского шаблона.</p><?php $yield($args) ?><p>После содержимого области родительского шаблона.</p>

<?php }) ?>

• $yield — вызов генерации содержимого областиродительского шаблона

• $args — массив параметров блока, как правилопередается без изменений

Выполнение снизу вверх, отпроизводного к родительскому шаблону

17 / 37

Page 18: Простая функциональная шаблонизация в PHP

ФлагиНаборы значений, задаваемые производными шаблонами.

// html.phtml$this->is(function (array $args, $yield) { ?>..<script>

require([<?= "’" . implode("’,’", $this->getFlags(’require’)) . "’" ?>],function () {});

</script>...

// page.phtml<?php $this->flags(’require’, array(’common’)) ?><?php $this->is(’html’, function(array $args, $yield) ...

// news.phtml<?php $this->flags(’require’, array(’news’, ’social’)) ?><?php $this->is(’page’, function (array $args, $yield) ...

// Результат для news:<script>

require([ ’common’, ’news’, ’social’ ], function () {});</script>

18 / 37

Page 19: Простая функциональная шаблонизация в PHP

Флаги: использование• часть определения шаблона, устанавливаются призагрузке, не меняются (многократном выполнении)

• значения по умолчанию — просто true• можно задавать произвольные значения, еслииспользовать ассоциативный массив

• можно получить не только имена, но и значения спомощью дополнительного признака в getFlags()

• порядок следования — от родительского шаблона кпроизводным

<?php $this->flags(’values’, array(’f1’ => 1, ’f2’ => 2)); ?>

<?php foreach ($this->getFlags(’values’, true) as $flag => $value) ... ?>

19 / 37

Page 20: Простая функциональная шаблонизация в PHP

Выполнение шаблона• шаблон = выполняемый объект• выполнение шаблона приводит к генерации егосодержимого без буферизации

• если нужен строковый результат — метод render()

Используя набор шаблонов, например, из другого шаблона:<?php $bundle->call(’template’, [ ’title’ => ’test’, ... ]); ?>

// Или индивидуально вызывая объект:$template = $bundle[’template’];$template([ ’title’ => ’test’, ’...’ ]);

// Если результат нужен в виде строки:$text = $template->render([ ’title’ => ’test’, ... ]);

20 / 37

Page 21: Простая функциональная шаблонизация в PHP

Данные шаблона

• шаблон вызывается с набором параметров $args• тип данных набора определяется типом аргументафункции шаблона

• в большинстве случаев — просто массив• рекомендуется использовать плоские массивы бездополнительных уровень вложенности

• иерархия элементов (если необходимо) — в имени ключа(page.meta.title и т.д.)

• в зависимости от ситуации, иногда выгоднее простопередать в вызываемый шаблон параметры вызывающего,иногда — сформировать новый массив параметров.

21 / 37

Page 22: Простая функциональная шаблонизация в PHP

Вспомогательные функцииШаблонов мало, нужны вспомогательные функции (helpers).

• вызываются много (еще много, много) раз• должны работать максимально быстро, динамическаядиспетчеризация вызова — плохая идея

• группируем функции в простые процедурные классы

<?php use imp\ext\HTML; ?>

<?php $this->is(function(array $data, $yield) { ?><p>Hello, <?= HTML::escape($data[’name’]) ?></p>

<?php }) ?>

22 / 37

Page 23: Простая функциональная шаблонизация в PHP

Структурированиешаблонов

23 / 37

Page 24: Простая функциональная шаблонизация в PHP

Главный антипаттернСэндвич привет Битриксу

<?php include(’top.phtml’); ?><?= $content ?><?php include(’bottom.phtml’); ?>

Два файла вместо одного, неудобно сопровождать, тяжелоискать ошибки, невозможно использовать правильнуюабстракцию, все сводится к include.

Не надо так делать.24 / 37

Page 25: Простая функциональная шаблонизация в PHP

Иерархия шаблонов• начинаем с базового шаблона страницы — заголовки,меты, скрипты

• выполняем специализацию шаблона для различныхразделов сайта

• (без необходимости) не делаем различий на уровнекорневого шаблона для главной и рабочих страниц

• добавляем специфический контент в производныхшаблонах с помощью блоков

• где нужно, отдельно вызываем шаблоны компонент• меньше условий, больше наследования (да, наследования)

В идеале — простая иерархическая структура, каждыйпроизводный шаблон дополняет, но не меняет родительский.

25 / 37

Page 26: Простая функциональная шаблонизация в PHP

Пример структурыsite/ - шаблоны сайта

html - общий шаблон (js, css, meta)page(html) - страница сайта (+header, +footer, +menu, +sidebar)

pages/ - шаблоны страниц сайта, загружаются контроллеромfront(page) - главная страницаsecondary(page) - рабочая страница (+breadcrumbs)news(secondary) - (+календарь в одном из блоков)catalog(secondary) - (+дополнительная навигация и баннер в блоке)product(catalog) - (+блок «смотри также» в sidebar)

news/calendar - компонент на странице news

showcase - витрина, на главной и страницах каталогаshowcase/

front - вариант витрины для главнойcategory - вариант витрины для каталога

...

26 / 37

Page 27: Простая функциональная шаблонизация в PHP

Шаблоны компонентов

• можно использовать наследование• можно использовать блоки (без фанатизма , )• без inline-скриптов (для этого есть flight-компоненты)• если в родительском шаблоне $yield вызывается в цикле,производный шаблон может переопределятьповторяющуюся часть (например, для витрины)

• шаблон компонента полностью изолирован отсодержащего его шаблона и это правильно

• поэтому может быть выведен отдельно на страницеруководства по стилям

27 / 37

Page 28: Простая функциональная шаблонизация в PHP

Процесс разработкипрограммирование и верстка

28 / 37

Page 29: Простая функциональная шаблонизация в PHP

Наша проблема• верстальщик — статические прототипы• программист — все остальное

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

Долой статические прототипы!

• верстальщик работает с шаблонами• не готова серверная часть — используем тестовые данные• отдельный ресурс приложения для тестирования шаблонов

29 / 37

Page 30: Простая функциональная шаблонизация в PHP

TemplateResource// Минимальное приложение для разработки шаблонов:include(’../lib/autoload.php’);

use imp\text\template\app\TemplateResource; // - шаблонный ресурсuse imp\text\template\TemplateBundle; // - набор шаблонов

use imp\http\app\Application; // - приложениеuse imp\http\app\Services; // - константы сервисовuse imp\http\Request; // - запросuse imp\http\server\Server; // - обработка запроса

$app = new Application();$app->service(Services::TEMPLATE, function (Application $app) {

return new TemplateBundle(__DIR__ . ’/../tpl’); // - шаблоны в tpl})->path(’proto’, function ($request, $match) use ($app) {

$app->rmatch(TemplateResource::SLUG,new TemplateResource($app, __DIR__ . ’/../proto’));

});

Server::send($app(Server::request()));30 / 37

Page 31: Простая функциональная шаблонизация в PHP

PrototypeApplication

А попроще нельзя? Можно, используя готовый класс:

include(’../ext/imp/lib/autoload.php’);

use imp\http\server\Server;use imp\ext\template\app\PrototypeApplication;

Server::run(new PrototypeApplication());

31 / 37

Page 32: Простая функциональная шаблонизация в PHP

Генерация прототиповproto/front.php

<?php return [’site.pages.front’, // <- первый элемент — используемый шаблон’document.title’ => ’Front page’,’document.description’ => ’Front page description’,’cart.count’ => 2,’cart.total’ => 2500,’page.menu-top’ => [[ ’title’ => ’Home’, ’active’ => true ],[ ’title’ => ’My Account’ ],...

];

http://site.com/proto/front.php32 / 37

Page 33: Простая функциональная шаблонизация в PHP

Определение прототипа

• PHP — возможность программной генерации данных• выполняется в контексте ресурса приложения→ можноиспользовать сервис данных и т.д.

• специальный файл _.php — вызывается перед загрузкойлюбого файла каталога

• набор вспомогательных методов Data: генерация объектовданных, случайные наборы данных и т.д.

• нет реализации — можно начинать с простых объектов• по мере реализации — заменяем на настоящие данные

33 / 37

Page 34: Простая функциональная шаблонизация в PHP

Прототипы• одни и те же шаблоны для прототипов и реальных страниц• могут соответствовать отдельным страницам сайта или ихразным вариантам

• могут состоять из отдельных компонентов страниц• могут образовывать руководство по стилю, развивающеесявместе с сайтом

• позволяют отлаживать JavaScript-компоненты• использование runway и Flight устраняет различие междупрототипами и реальными страницами.

протосайт ,→ реальный сайт

34 / 37

Page 35: Простая функциональная шаблонизация в PHP

Данные прототипов• реальные данные всегда лучше• нет — начинаем с тестовых, потом адаптируем шаблон

use imp\fake\Data;

use imp\fake\Data;return [

’catalog.items’ => Data::collection(Data::make(Data::rand(10, 20), function () {

return Data::entity([’id’ => Data::id(),’title’ => Data::any([ ’Костюм’, ’Рубашка’, ’Блейзер’, ’Поло’ ]),’size’ => Data::any([ ’S’, ’M’, ’L’, ’XL’, ’XXL’ ]),’price’ => Data::rand(1000, 5000)

]);});

)];

35 / 37

Page 36: Простая функциональная шаблонизация в PHP

imp\fake\Data• Data::collection(), Data::entity() — коллекции исущности, c т.з. шаблона не должно быть большой разницысо слоем модели

• Data::any(), Data::make() — возвращают массивы, измассива легко сделать коллекцию

• Data::make() — генерация набора данных по исходномунабору значений или диапазону

• Data::any() — N различных значений из набора (Флойд)

$some = Data::any(3, Data::make([ 1 => ’one’, 2 => ’two’, 3 => ’three’, 4 => ’four’, 5 => ’five’ ],function ($value, $key) { return "$key. Item $value"; }

));

36 / 37

Page 37: Простая функциональная шаблонизация в PHP

Итого• можно писать структурированные шаблоны на PHP• функциональный шаблонный движок не обязательнобольшой и сложный

• анемичная модель хорошо подходит для шаблонизации• верстальщик — это вообще-то не только HTML, но еще ишаблонизация и JavaScript

• верстка и реализация функционала могут выполнятьсяпараллельно

• актуальный Style Guide с минимумом дополнительныхзатрат возможен даже в условиях конвейера

privatehttps://bitbucket.org/interlabs/imp/

37 / 37