Nette Documentation Preview

syntax
Наследование шаблонов и возможность повторного использования
************************************************************

.[perex]
Механизмы повторного использования и наследования шаблонов повышают вашу производительность, поскольку каждый шаблон содержит только уникальное содержимое, а повторяющиеся элементы и структуры используются повторно. Мы представляем три концепции: [наследование макета |#Layout-Inheritance], [горизонтальное повторное использование |#Horizontal-Reuse] и [наследование единиц |#Unit-Inheritance].

Концепция наследования шаблонов Latte похожа на наследование классов в PHP. Вы определяете **родительский шаблон**, от которого могут отталкиваться другие **потомственные шаблоны** и переопределять части родительского шаблона. Это отлично работает, когда элементы имеют общую структуру. Звучит сложно? Не волнуйтесь, это не так.


Наследование макета `{layout}` .{toc: Layout Inheritance}
=========================================================

Давайте рассмотрим наследование шаблонов макета на примере. Это родительский шаблон, который мы назовем для примера `layout.latte`, и он определяет HTML-скелет документа.

```latte
<!doctype html>
<html lang="en">
<head>
	<title>{block title}{/block}</title>
	<link rel="stylesheet" href="style.css">
</head>
<body>
	<div id="content">
		{block content}{/block}
	</div>
	<div id="footer">
		{block footer}&copy; Copyright 2008{/block}
	</div>
</body>
</html>
```

Теги `{block}` определяют три блока, которые дочерние шаблоны могут заполнять. Все, что делает тег block, это сообщает шаблонизатору, что дочерний шаблон может переопределить эти части шаблона, определив свой собственный блок с тем же именем.

Дочерний шаблон может выглядеть следующим образом:

```latte
{layout 'layout.latte'}

{block title}My amazing blog{/block}

{block content}
	<p>Welcome to my awesome homepage.</p>
{/block}
```

Ключевым здесь является тег `{layout}`. Он сообщает шаблонизатору, что этот шаблон "расширяет" другой шаблон. Когда Latte рендерит этот шаблон, сначала он находит родителя - в данном случае `layout.latte`.

В этот момент шаблонизатор заметит три блочных тега в `layout.latte` и заменит эти блоки содержимым дочернего шаблона. Обратите внимание, что поскольку дочерний шаблон не определил блок *footer*, вместо него используется содержимое родительского шаблона. Содержимое внутри тега `{block}` в родительском шаблоне всегда используется в качестве запасного варианта.

Вывод может выглядеть следующим образом:

```latte
<!doctype html>
<html lang="en">
<head>
	<title>My amazing blog</title>
	<link rel="stylesheet" href="style.css">
</head>
<body>
	<div id="content">
		<p>Welcome to my awesome homepage.</p>
	</div>
	<div id="footer">
		&copy; Copyright 2008
	</div>
</body>
</html>
```

В дочернем шаблоне блоки могут располагаться только либо на верхнем уровне, либо внутри другого блока, т.е:

```latte
{block content}
	<h1>{block title}Welcome to my awesome homepage{/block}</h1>
{/block}
```

Кроме того, блок всегда будет создаваться в независимо от того, будет ли окружающее `{if}` условие оценено как true или false. Вопреки тому, что вы можете подумать, этот шаблон действительно определяет блок.

```latte
{if false}
	{block head}
		<meta name="robots" content="noindex, follow">
	{/block}
{/if}
```

Если вы хотите, чтобы вывод внутри блока отображался условно, используйте следующее:

```latte
{block head}
	{if $condition}
		<meta name="robots" content="noindex, follow">
	{/if}
{/block}
```

Данные вне блоков в дочернем шаблоне выполняются до отрисовки шаблона макета, поэтому вы можете использовать его для определения переменных типа `{var $foo = bar}` и распространения данных на всю цепочку наследования:

```latte
{layout 'layout.latte'}
{var $robots = noindex}

...
```


Многоуровневое наследование .[#toc-multilevel-inheritance]
----------------------------------------------------------
Вы можете использовать столько уровней наследования, сколько необходимо. Одним из распространенных способов использования наследования макетов является следующий трехуровневый подход:

1) Создайте шаблон `layout.latte`, в котором будет храниться основной внешний вид вашего сайта.
2) Создайте шаблон `layout-SECTIONNAME.latte` для каждого раздела вашего сайта. Например, `layout-news.latte`, `layout-blog.latte` и т.д. Все эти шаблоны расширяют `layout.latte` и включают стили/дизайн для каждого раздела.
3) Создайте отдельные шаблоны для каждого типа страницы, например, для новостной статьи или записи в блоге. Эти шаблоны расширяют соответствующий шаблон раздела.


Динамическое наследование макета .[#toc-dynamic-layout-inheritance]
-------------------------------------------------------------------
Вы можете использовать переменную или любое выражение PHP в качестве имени родительского шаблона, таким образом, наследование может вести себя динамически:

```latte
{layout $standalone ? 'minimum.latte' : 'layout.latte'}
```

Вы также можете использовать Latte API для [автоматического |develop#Automatic-Layout-Lookup] выбора шаблона компоновки.


Советы .[#toc-tips]
-------------------
Вот несколько советов по работе с наследованием макета:

- Если вы используете `{layout}` в шаблоне, он должен быть первым тегом шаблона в этом шаблоне.

- Макет может [искаться автоматически |develop#automatic-layout-lookup] (как в [презентаторах |application:templates#Template Lookup]). В этом случае, если шаблон не должен иметь макета, он укажет на это с помощью тега `{layout none}`.

- Тег `{layout}` имеет псевдоним `{extends}`.

- Имя файла расширенного шаблона зависит от [загрузчика шаблонов |extending-latte#Loaders].

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


Блоки `{block}` .{toc: Blocks}
==============================

.[note]
См. также анонимные [`{block}` |tags#block]

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

```latte .{file: parent.latte}
{foreach $posts as $post}
{block post}
	<h1>{$post->title}</h1>
	<p>{$post->body}</p>
{/block}
{/foreach}
```

Если вы отобразите этот шаблон, результат будет точно таким же с тегами блока или без них. Блоки имеют доступ к переменным из внешних диапазонов. Это просто способ сделать его переопределяемым для дочернего шаблона:

```latte .{file: child.latte}
{layout 'parent.Latte'}

{block post}
	<article>
		<header>{$post->title}</header>
		<section>{$post->text}</section>
	</article>
{/block}
```

Теперь при рендеринге дочернего шаблона цикл будет использовать блок, определенный в дочернем шаблоне `child.Latte`, вместо блока, определенного в базовом шаблоне `parent.Latte`; выполненный шаблон будет эквивалентен следующему:

```latte
{foreach $posts as $post}
	<article>
		<header>{$post->title}</header>
		<section>{$post->text}</section>
	</article>
{/foreach}
```

Однако, если мы создадим новую переменную внутри именованного блока или заменим значение существующей переменной, изменение будет видно только внутри блока:

```latte
{var $foo = 'foo'}
{block post}
	{do $foo = 'new value'}
	{var $bar = 'bar'}
{/block}

foo: {$foo}                  // prints: foo
bar: {$bar ?? 'not defined'} // prints: not defined
```

Содержимое блока может быть изменено с помощью [фильтров |syntax#Filters]. В следующем примере удаляется весь HTML и приводится заголовок:

```latte
<title>{block title|stripHtml|capitalize}...{/block}</title>
```

Тег также может быть записан как [n:attribute |syntax#n-attributes]:

```latte
<article n:block=post>
	...
</article>
```


Локальные блоки .[#toc-local-blocks]
------------------------------------

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

```latte
{block local helper}
	...
{/block}
```


Печать блоков `{include}` .{toc: Printing Blocks}
-------------------------------------------------

.[note]
См. также [`{include file}` |tags#include]

Чтобы напечатать блок в определенном месте, используйте тег `{include blockname}`:

```latte
<title>{block title}{/block}</title>

<h1>{include title}</h1>
```

Вы также можете вывести блок из другого шаблона:

```latte
{include footer from 'main.latte'}
```

Выводимые блоки не имеют доступа к переменным активного контекста, за исключением случаев, когда блок определен в том же файле, куда он включен. Однако они имеют доступ к глобальным переменным.

Вы можете передавать переменные в блок следующим образом:

```latte
{include footer, foo: bar, id: 123}
```

Вы можете использовать переменную или любое выражение в PHP в качестве имени блока. В этом случае добавьте ключевое слово `block` перед переменной, чтобы при компиляции было известно, что это блок, а не [вставка шаблона |tags#include], имя которого также может быть в переменной:

```latte
{var $name = footer}
{include block $name}
```

Блок также может быть напечатан внутри себя, что полезно, например, при рендеринге древовидной структуры:

```latte
{define menu, $items}
<ul>
	{foreach $items as $item}
		<li>
		{if is_array($item)}
			{include menu, $item}
		{else}
			{$item}
		{/if}
		</li>
	{/foreach}
</ul>
{/define}
```

Вместо `{include menu, ...}` можно также написать `{include this, ...}`, где `this` означает текущий блок.

Выводимое содержимое можно изменять с помощью [фильтров |syntax#Filters]. В следующем примере удаляется весь HTML и ставится заголовок:

```latte
<title>{include heading|stripHtml|capitalize}</title>
```


Родительский блок .[#toc-parent-block]
--------------------------------------

Если вам нужно вывести содержимое блока из родительского шаблона, вам поможет оператор `{include parent}`. Это полезно, если вы хотите дополнить содержимое родительского блока, а не полностью его переопределить.

```latte
{block footer}
	{include parent}
	<a href="https://github.com/nette">GitHub</a>
	<a href="https://twitter.com/nettefw">Twitter</a>
{/block}
```


Определения `{define}` .{toc: Definitions}
------------------------------------------

Помимо блоков, в Latte существуют также "определения". Их можно сравнить с функциями в обычных языках программирования. Они полезны для повторного использования фрагментов шаблонов, чтобы не повторяться.

Latte стремится к простоте, поэтому в основном определения - это то же самое, что и блоки, и **все, что сказано о блоках, относится и к определениям**. Они отличаются от блоков тем, что:

1) они заключены в теги `{define}`
2) они отображаются только при вставке через `{include}`
3) для них можно задавать параметры, как для функций в PHP

```latte
{block foo}<p>Hello</p>{/block}
{* prints: <p>Hello</p> *}

{define bar}<p>World</p>{/define}
{* prints nothing *}

{include bar}
{* prints: <p>World</p> *}
```

Представьте, что у вас есть шаблон-помощник, содержащий набор определений для рисования HTML-форм.

```latte .{file: forms.latte}
{define input, $name, $value, $type = 'text'}
	<input type={$type} name={$name} value={$value}>
{/define}

{define textarea, $name, $value}
	<textarea name={$name}>{$value}</textarea>
{/define}
```

Аргументы определения всегда являются необязательными со значением по умолчанию `null`, если не указано значение по умолчанию (здесь `'text'` является значением по умолчанию для `$type`). Типы параметров также могут быть объявлены: `{define input, string $name, ...}`.

Шаблон с определениями загружается с помощью [`{import}` |#horizontal-reuse]. Сами определения отображаются так же, как и [блоки |#Printing Blocks]:

```latte
<p>{include input, 'password', null, 'password'}</p>
<p>{include textarea, 'comment'}</p>
```

Определения не имеют доступа к переменным активного контекста, но имеют доступ к глобальным переменным.


Динамические имена блоков .[#toc-dynamic-block-names]
-----------------------------------------------------

Latte позволяет очень гибко определять блоки, потому что имя блока может быть любым выражением PHP. В данном примере определены три блока с именами `hi-Peter`, `hi-John` и `hi-Mary`:

```latte .{file: parent.latte}
{foreach [Peter, John, Mary] as $name}
	{block "hi-$name"}Hi, I am {$name}.{/block}
{/foreach}
```

Например, мы можем переопределить только один блок в дочернем шаблоне:

```latte .{file: child.latte}
{block hi-John}Hello. I am {$name}.{/block}
```

Таким образом, вывод будет выглядеть следующим образом:

```latte
Hi, I am Peter.
Hello. I am John.
Hi, I am Mary.
```


Проверка существования блока `{ifset}` .{toc: Checking Block Existence}
-----------------------------------------------------------------------

.[note]
См. также [`{ifset $var}` |tags#ifset-elseifset]

Используйте тест `{ifset blockname}`, чтобы проверить, существует ли блок (или несколько блоков) в текущем контексте:

```latte
{ifset footer}
	...
{/ifset}

{ifset footer, header, main}
	...
{/ifset}
```

В качестве имени блока вы можете использовать переменную или любое выражение в PHP. В этом случае добавьте ключевое слово `block` перед переменной, чтобы было понятно, что проверяется не [она |tags#ifset-elseifset]:

```latte
{ifset block $name}
	...
{/ifset}
```

Информация о существовании блоков также возвращается функцией [`hasBlock()` |functions#hasBlock]:

```latte
{if hasBlock(header) || hasBlock(footer)}
	...
{/if}
```


Советы .[#toc-tips]
-------------------
Вот несколько советов по работе с блоками:

- Последний блок верхнего уровня не обязательно должен иметь закрывающий тег (блок заканчивается вместе с концом документа). Это упрощает написание дочерних шаблонов, в которых один основной блок.

- Для повышения удобочитаемости вы можете по желанию дать имя тегу `{/block}`, например `{/block footer}`. Однако имя должно совпадать с именем блока. В больших шаблонах этот прием помогает увидеть, какие теги блоков закрываются.

- Вы не можете напрямую определить несколько блочных тегов с одинаковым именем в одном шаблоне. Но этого можно добиться, используя [динамические имена блоков |#Dynamic-Block-Names].

- Вы можете использовать [n:attributes |syntax#n-attributes] для определения таких блоков, как `<h1 n:block=title>Welcome to my awesome homepage</h1>`

- Блоки также можно использовать без имен, только для применения [фильтров |syntax#Filters] к выводу: `{block|strip} hello {/block}`


Горизонтальное повторное использование `{import}` .{toc: Horizontal Reuse}
==========================================================================

Горизонтальное повторное использование - это третий механизм повторного использования и наследования в Latte. Он позволяет загружать блоки из других шаблонов. Это похоже на создание файла с вспомогательными функциями в PHP и последующую его загрузку с помощью `require`.

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

Пусть имеется набор определений блоков:

```latte .{file: blocks.latte}
{block sidebar}...{/block}

{block menu}...{/block}
```

Используя команду `{import}`, импортируйте все блоки и [определения |#definitions], определенные в `blocks.latte`, в другой шаблон:

```latte .{file: child.latte}
{import 'blocks.latte'}

{* Теперь можно использовать блоки sidebar и menu *}
```

Если импортировать блоки в родительский шаблон (т.е. использовать `{import}` в `layout.latte`), то они будут доступны и во всех дочерних шаблонах, что очень удобно.

Шаблон, который предполагается импортировать (например, `blocks.latte`), не должен [расширять |#Layout Inheritance] другой шаблон, т.е. использовать `{layout}`. Однако он может импортировать другие шаблоны.

Тег `{import}` должен быть первым тегом шаблона после `{layout}`. Имя шаблона может быть любым выражением PHP:

```latte
{import $ajax ? 'ajax.latte' : 'not-ajax.latte'}
```

Вы можете использовать столько выражений `{import}`, сколько хотите, в любом данном шаблоне. Если два импортированных шаблона определяют один и тот же блок, побеждает первый. Однако наивысший приоритет отдается главному шаблону, который может перезаписать любой импортированный блок.

Содержимое перезаписываемых блоков можно сохранить, вставив блок так же, как и [родительский блок |#parent block]:

```latte
{layout 'layout.latte'}

{import 'blocks.latte'}

{block sidebar}
	{include parent}
{/block}

{block title}...{/block}
{block content}...{/block}
```

В этом примере `{include parent}` будет корректно вызывать блок `sidebar` из шаблона `blocks.latte`.


Наследование блоков `{embed}` .{toc: Unit Inheritance}
======================================================

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

В наследовании блоков ключевым является тег `{embed}`. Он сочетает в себе поведение `{include}` и `{layout}`. Он позволяет включать содержимое другого шаблона или блока и, по желанию, передавать переменные, как это делает `{include}`. Он также позволяет переопределять любой блок, определенный внутри включенного шаблона, как это делает `{layout}`.

Для примера мы будем использовать элемент складного аккордеона. Давайте посмотрим на скелет элемента в шаблоне `collapsible.latte`:

```latte
<section class="collapsible {$modifierClass}">
	<h4 class="collapsible__title">
		{block title}{/block}
	</h4>

	<div class="collapsible__content">
		{block content}{/block}
	</div>
</section>
```

Теги `{block}` определяют два блока, которые могут заполнять дочерние шаблоны. Да, как и в случае с родительским шаблоном в шаблоне наследования макета. Вы также видите переменную `$modifierClass`.

Давайте используем наш элемент в шаблоне. Здесь на помощь приходит `{embed}`. Это супер мощный набор, который позволяет нам делать все: включать содержимое шаблона элемента, добавлять к нему переменные и добавлять к нему блоки с пользовательским HTML:

```latte
{embed 'collapsible.latte', modifierClass: my-style}
	{block title}
		Hello World
	{/block}

	{block content}
		<p>Lorem ipsum dolor sit amet, consectetuer adipiscing
		elit. Nunc dapibus tortor vel mi dapibus sollicitudin.</p>
	{/block}
{/embed}
```

Вывод может выглядеть следующим образом:

```latte
<section class="collapsible my-style">
	<h4 class="collapsible__title">
		Hello World
	</h4>

	<div class="collapsible__content">
		<p>Lorem ipsum dolor sit amet, consectetuer adipiscing
		elit. Nunc dapibus tortor vel mi dapibus sollicitudin.</p>
	</div>
</section>
```

Блоки внутри тегов embed образуют отдельный слой, независимый от других блоков. Поэтому они могут иметь то же имя, что и блок вне embed, и никак на них не влияют. Используя тег [include |#Printing-Blocks] внутри тегов `{embed}`, вы можете вставлять созданные здесь блоки, блоки из встроенного шаблона (которые *не являются* [локальными |#Local-Blocks]), а также блоки из основного шаблона, которые *являются* локальными. Вы также можете [импортировать блоки |#Horizontal-Reuse] из других файлов:

```latte
{block outer}…{/block}
{block local hello}…{/block}

{embed 'collapsible.latte', modifierClass: my-style}
	{import 'blocks.latte'}

	{block inner}…{/block}

	{block title}
		{include inner} {* works, block is defined inside embed *}
		{include hello} {* works, block is local in this template *}
		{include content} {* works, block is defined in embedded template *}
		{include aBlockDefinedInImportedTemplate} {* works *}
		{include outer} {* does not work! - block is in outer layer *}
	{/block}
{/embed}
```

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

С помощью `{embed}` можно вставлять не только шаблоны, но и другие блоки, поэтому предыдущий пример можно написать так:

```latte
{define collapsible}
<section class="collapsible {$modifierClass}">
	<h4 class="collapsible__title">
		{block title}{/block}
	</h4>
	...
</section>
{/define}


{embed collapsible, modifierClass: my-style}
	{block title}
		Hello World
	{/block}
	...
{/embed}
```

Если мы передаем выражение в `{embed}` и не ясно, что это - блок или имя файла, добавьте ключевое слово `block` или `file`:

```latte
{embed block $name} ... {/embed}
```


Примеры использования .[#toc-use-cases]
=======================================

В Latte существуют различные виды наследования и повторного использования кода. Давайте обобщим основные понятия для большей наглядности:


`{include template}`
--------------------

**Use Case:** Использование `header.latte` и `footer.latte` внутри `layout.latte`.

`header.latte`

```latte
<nav>
   <div>Home</div>
   <div>About</div>
</nav>
```

`footer.latte`

```latte
<footer>
   <div>Copyright</div>
</footer>
```

`layout.latte`

```latte
{include 'header.latte'}

<main>{block main}{/block}</main>

{include 'footer.latte'}
```


`{layout}`
----------

**Пример использования**: Расширение `layout.latte` внутри `homepage.latte` и `about.latte`.

`layout.latte`

```latte
{include 'header.latte'}

<main>{block main}{/block}</main>

{include 'footer.latte'}
```

`homepage.latte`

```latte
{layout 'layout.latte'}

{block main}
	<p>Homepage</p>
{/block}
```

`about.latte`

```latte
{layout 'layout.latte'}

{block main}
	<p>About page</p>
{/block}
```


`{import}`
----------

**Пользовательский случай**: `sidebar.latte` в `single.product.latte` и `single.service.latte`.

`sidebar.latte`

```latte
{block sidebar}<aside>This is sidebar</aside>{/block}
```

`single.product.latte`

```latte
{layout 'product.layout.latte'}

{import 'sidebar.latte'}

{block main}<main>Product page</main>{/block}
```

`single.service.latte`

```latte
{layout 'service.layout.latte'}

{import 'sidebar.latte'}

{block main}<main>Service page</main>{/block}
```


`{define}`
----------

**Пример использования**: Функция, которая получает некоторые переменные и выводит некоторую разметку.

`form.latte`

```latte
{define form-input, $name, $value, $type = 'text'}
	<input type={$type} name={$name} value={$value}>
{/define}
```

`profile.service.latte`

```latte
{import 'form.latte'}

<form action="" method="post">
	<div>{include form-input, username}</div>
	<div>{include form-input, password}</div>
	<div>{include form-input, submit, Submit, submit}</div>
</form>
```


`{embed}`
---------

**Пример использования**: Встраивание `pagination.latte` в `product.table.latte` и `service.table.latte`.

`pagination.latte`

```latte
<div id="pagination">
	<div>{block first}{/block}</div>

	{for $i = $min + 1; $i < $max - 1; $i++}
		<div>{$i}</div>
	{/for}

	<div>{block last}{/block}</div>
</div>
```

`product.table.latte`

```latte
{embed 'pagination.latte', min: 1, max: $products->count}
	{block first}First Product Page{/block}
	{block last}Last Product Page{/block}
{/embed}
```

`service.table.latte`

```latte
{embed 'pagination.latte', min: 1, max: $services->count}
	{block first}First Service Page{/block}
	{block last}Last Service Page{/block}
{/embed}
```

Наследование шаблонов и возможность повторного использования

Механизмы повторного использования и наследования шаблонов повышают вашу производительность, поскольку каждый шаблон содержит только уникальное содержимое, а повторяющиеся элементы и структуры используются повторно. Мы представляем три концепции: наследование макета, горизонтальное повторное использование и наследование единиц.

Концепция наследования шаблонов Latte похожа на наследование классов в PHP. Вы определяете родительский шаблон, от которого могут отталкиваться другие потомственные шаблоны и переопределять части родительского шаблона. Это отлично работает, когда элементы имеют общую структуру. Звучит сложно? Не волнуйтесь, это не так.

Наследование макета {layout}

Давайте рассмотрим наследование шаблонов макета на примере. Это родительский шаблон, который мы назовем для примера layout.latte, и он определяет HTML-скелет документа.

<!doctype html>
<html lang="en">
<head>
	<title>{block title}{/block}</title>
	<link rel="stylesheet" href="style.css">
</head>
<body>
	<div id="content">
		{block content}{/block}
	</div>
	<div id="footer">
		{block footer}&copy; Copyright 2008{/block}
	</div>
</body>
</html>

Теги {block} определяют три блока, которые дочерние шаблоны могут заполнять. Все, что делает тег block, это сообщает шаблонизатору, что дочерний шаблон может переопределить эти части шаблона, определив свой собственный блок с тем же именем.

Дочерний шаблон может выглядеть следующим образом:

{layout 'layout.latte'}

{block title}My amazing blog{/block}

{block content}
	<p>Welcome to my awesome homepage.</p>
{/block}

Ключевым здесь является тег {layout}. Он сообщает шаблонизатору, что этот шаблон „расширяет“ другой шаблон. Когда Latte рендерит этот шаблон, сначала он находит родителя – в данном случае layout.latte.

В этот момент шаблонизатор заметит три блочных тега в layout.latte и заменит эти блоки содержимым дочернего шаблона. Обратите внимание, что поскольку дочерний шаблон не определил блок footer, вместо него используется содержимое родительского шаблона. Содержимое внутри тега {block} в родительском шаблоне всегда используется в качестве запасного варианта.

Вывод может выглядеть следующим образом:

<!doctype html>
<html lang="en">
<head>
	<title>My amazing blog</title>
	<link rel="stylesheet" href="style.css">
</head>
<body>
	<div id="content">
		<p>Welcome to my awesome homepage.</p>
	</div>
	<div id="footer">
		&copy; Copyright 2008
	</div>
</body>
</html>

В дочернем шаблоне блоки могут располагаться только либо на верхнем уровне, либо внутри другого блока, т.е:

{block content}
	<h1>{block title}Welcome to my awesome homepage{/block}</h1>
{/block}

Кроме того, блок всегда будет создаваться в независимо от того, будет ли окружающее {if} условие оценено как true или false. Вопреки тому, что вы можете подумать, этот шаблон действительно определяет блок.

{if false}
	{block head}
		<meta name="robots" content="noindex, follow">
	{/block}
{/if}

Если вы хотите, чтобы вывод внутри блока отображался условно, используйте следующее:

{block head}
	{if $condition}
		<meta name="robots" content="noindex, follow">
	{/if}
{/block}

Данные вне блоков в дочернем шаблоне выполняются до отрисовки шаблона макета, поэтому вы можете использовать его для определения переменных типа {var $foo = bar} и распространения данных на всю цепочку наследования:

{layout 'layout.latte'}
{var $robots = noindex}

...

Многоуровневое наследование

Вы можете использовать столько уровней наследования, сколько необходимо. Одним из распространенных способов использования наследования макетов является следующий трехуровневый подход:

  1. Создайте шаблон layout.latte, в котором будет храниться основной внешний вид вашего сайта.
  2. Создайте шаблон layout-SECTIONNAME.latte для каждого раздела вашего сайта. Например, layout-news.latte, layout-blog.latte и т.д. Все эти шаблоны расширяют layout.latte и включают стили/дизайн для каждого раздела.
  3. Создайте отдельные шаблоны для каждого типа страницы, например, для новостной статьи или записи в блоге. Эти шаблоны расширяют соответствующий шаблон раздела.

Динамическое наследование макета

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

{layout $standalone ? 'minimum.latte' : 'layout.latte'}

Вы также можете использовать Latte API для автоматического выбора шаблона компоновки.

Советы

Вот несколько советов по работе с наследованием макета:

  • Если вы используете {layout} в шаблоне, он должен быть первым тегом шаблона в этом шаблоне.
  • Макет может искаться автоматически (как в презентаторах). В этом случае, если шаблон не должен иметь макета, он укажет на это с помощью тега {layout none}.
  • Тег {layout} имеет псевдоним {extends}.
  • Имя файла расширенного шаблона зависит от загрузчика шаблонов.
  • Вы можете иметь столько блоков, сколько хотите. Помните, что дочерние шаблоны не обязаны определять все родительские блоки, поэтому вы можете заполнить разумные значения по умолчанию в нескольких блоках, а затем определить только те, которые вам нужны позже.

Блоки {block}

См. также анонимные {block}

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

{foreach $posts as $post}
{block post}
	<h1>{$post->title}</h1>
	<p>{$post->body}</p>
{/block}
{/foreach}

Если вы отобразите этот шаблон, результат будет точно таким же с тегами блока или без них. Блоки имеют доступ к переменным из внешних диапазонов. Это просто способ сделать его переопределяемым для дочернего шаблона:

{layout 'parent.Latte'}

{block post}
	<article>
		<header>{$post->title}</header>
		<section>{$post->text}</section>
	</article>
{/block}

Теперь при рендеринге дочернего шаблона цикл будет использовать блок, определенный в дочернем шаблоне child.Latte, вместо блока, определенного в базовом шаблоне parent.Latte; выполненный шаблон будет эквивалентен следующему:

{foreach $posts as $post}
	<article>
		<header>{$post->title}</header>
		<section>{$post->text}</section>
	</article>
{/foreach}

Однако, если мы создадим новую переменную внутри именованного блока или заменим значение существующей переменной, изменение будет видно только внутри блока:

{var $foo = 'foo'}
{block post}
	{do $foo = 'new value'}
	{var $bar = 'bar'}
{/block}

foo: {$foo}                  // prints: foo
bar: {$bar ?? 'not defined'} // prints: not defined

Содержимое блока может быть изменено с помощью фильтров. В следующем примере удаляется весь HTML и приводится заголовок:

<title>{block title|stripHtml|capitalize}...{/block}</title>

Тег также может быть записан как n:attribute:

<article n:block=post>
	...
</article>

Локальные блоки

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

{block local helper}
	...
{/block}

Печать блоков {include}

См. также {include file}

Чтобы напечатать блок в определенном месте, используйте тег {include blockname}:

<title>{block title}{/block}</title>

<h1>{include title}</h1>

Вы также можете вывести блок из другого шаблона:

{include footer from 'main.latte'}

Выводимые блоки не имеют доступа к переменным активного контекста, за исключением случаев, когда блок определен в том же файле, куда он включен. Однако они имеют доступ к глобальным переменным.

Вы можете передавать переменные в блок следующим образом:

{include footer, foo: bar, id: 123}

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

{var $name = footer}
{include block $name}

Блок также может быть напечатан внутри себя, что полезно, например, при рендеринге древовидной структуры:

{define menu, $items}
<ul>
	{foreach $items as $item}
		<li>
		{if is_array($item)}
			{include menu, $item}
		{else}
			{$item}
		{/if}
		</li>
	{/foreach}
</ul>
{/define}

Вместо {include menu, ...} можно также написать {include this, ...}, где this означает текущий блок.

Выводимое содержимое можно изменять с помощью фильтров. В следующем примере удаляется весь HTML и ставится заголовок:

<title>{include heading|stripHtml|capitalize}</title>

Родительский блок

Если вам нужно вывести содержимое блока из родительского шаблона, вам поможет оператор {include parent}. Это полезно, если вы хотите дополнить содержимое родительского блока, а не полностью его переопределить.

{block footer}
	{include parent}
	<a href="https://github.com/nette">GitHub</a>
	<a href="https://twitter.com/nettefw">Twitter</a>
{/block}

Определения {define}

Помимо блоков, в Latte существуют также „определения“. Их можно сравнить с функциями в обычных языках программирования. Они полезны для повторного использования фрагментов шаблонов, чтобы не повторяться.

Latte стремится к простоте, поэтому в основном определения – это то же самое, что и блоки, и все, что сказано о блоках, относится и к определениям. Они отличаются от блоков тем, что:

  1. они заключены в теги {define}
  2. они отображаются только при вставке через {include}
  3. для них можно задавать параметры, как для функций в PHP
{block foo}<p>Hello</p>{/block}
{* prints: <p>Hello</p> *}

{define bar}<p>World</p>{/define}
{* prints nothing *}

{include bar}
{* prints: <p>World</p> *}

Представьте, что у вас есть шаблон-помощник, содержащий набор определений для рисования HTML-форм.

{define input, $name, $value, $type = 'text'}
	<input type={$type} name={$name} value={$value}>
{/define}

{define textarea, $name, $value}
	<textarea name={$name}>{$value}</textarea>
{/define}

Аргументы определения всегда являются необязательными со значением по умолчанию null, если не указано значение по умолчанию (здесь 'text' является значением по умолчанию для $type). Типы параметров также могут быть объявлены: {define input, string $name, ...}.

Шаблон с определениями загружается с помощью {import}. Сами определения отображаются так же, как и блоки:

<p>{include input, 'password', null, 'password'}</p>
<p>{include textarea, 'comment'}</p>

Определения не имеют доступа к переменным активного контекста, но имеют доступ к глобальным переменным.

Динамические имена блоков

Latte позволяет очень гибко определять блоки, потому что имя блока может быть любым выражением PHP. В данном примере определены три блока с именами hi-Peter, hi-John и hi-Mary:

{foreach [Peter, John, Mary] as $name}
	{block "hi-$name"}Hi, I am {$name}.{/block}
{/foreach}

Например, мы можем переопределить только один блок в дочернем шаблоне:

{block hi-John}Hello. I am {$name}.{/block}

Таким образом, вывод будет выглядеть следующим образом:

Hi, I am Peter.
Hello. I am John.
Hi, I am Mary.

Проверка существования блока {ifset}

См. также {ifset $var}

Используйте тест {ifset blockname}, чтобы проверить, существует ли блок (или несколько блоков) в текущем контексте:

{ifset footer}
	...
{/ifset}

{ifset footer, header, main}
	...
{/ifset}

В качестве имени блока вы можете использовать переменную или любое выражение в PHP. В этом случае добавьте ключевое слово block перед переменной, чтобы было понятно, что проверяется не она:

{ifset block $name}
	...
{/ifset}

Информация о существовании блоков также возвращается функцией hasBlock():

{if hasBlock(header) || hasBlock(footer)}
	...
{/if}

Советы

Вот несколько советов по работе с блоками:

  • Последний блок верхнего уровня не обязательно должен иметь закрывающий тег (блок заканчивается вместе с концом документа). Это упрощает написание дочерних шаблонов, в которых один основной блок.
  • Для повышения удобочитаемости вы можете по желанию дать имя тегу {/block}, например {/block footer}. Однако имя должно совпадать с именем блока. В больших шаблонах этот прием помогает увидеть, какие теги блоков закрываются.
  • Вы не можете напрямую определить несколько блочных тегов с одинаковым именем в одном шаблоне. Но этого можно добиться, используя динамические имена блоков.
  • Вы можете использовать n:attributes для определения таких блоков, как <h1 n:block=title>Welcome to my awesome homepage</h1>
  • Блоки также можно использовать без имен, только для применения фильтров к выводу: {block|strip} hello {/block}

Горизонтальное повторное использование {import}

Горизонтальное повторное использование – это третий механизм повторного использования и наследования в Latte. Он позволяет загружать блоки из других шаблонов. Это похоже на создание файла с вспомогательными функциями в PHP и последующую его загрузку с помощью require.

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

Пусть имеется набор определений блоков:

{block sidebar}...{/block}

{block menu}...{/block}

Используя команду {import}, импортируйте все блоки и определения, определенные в blocks.latte, в другой шаблон:

{import 'blocks.latte'}

{* Теперь можно использовать блоки sidebar и menu *}

Если импортировать блоки в родительский шаблон (т.е. использовать {import} в layout.latte), то они будут доступны и во всех дочерних шаблонах, что очень удобно.

Шаблон, который предполагается импортировать (например, blocks.latte), не должен расширять другой шаблон, т.е. использовать {layout}. Однако он может импортировать другие шаблоны.

Тег {import} должен быть первым тегом шаблона после {layout}. Имя шаблона может быть любым выражением PHP:

{import $ajax ? 'ajax.latte' : 'not-ajax.latte'}

Вы можете использовать столько выражений {import}, сколько хотите, в любом данном шаблоне. Если два импортированных шаблона определяют один и тот же блок, побеждает первый. Однако наивысший приоритет отдается главному шаблону, который может перезаписать любой импортированный блок.

Содержимое перезаписываемых блоков можно сохранить, вставив блок так же, как и родительский блок:

{layout 'layout.latte'}

{import 'blocks.latte'}

{block sidebar}
	{include parent}
{/block}

{block title}...{/block}
{block content}...{/block}

В этом примере {include parent} будет корректно вызывать блок sidebar из шаблона blocks.latte.

Наследование блоков {embed}

Наследование блоков переносит идею наследования макетов на уровень фрагментов контента. В то время как наследование макета работает со „скелетами документов“, которые оживляются дочерними шаблонами, наследование единиц позволяет создавать скелеты для меньших единиц содержимого и повторно использовать их в любом месте.

В наследовании блоков ключевым является тег {embed}. Он сочетает в себе поведение {include} и {layout}. Он позволяет включать содержимое другого шаблона или блока и, по желанию, передавать переменные, как это делает {include}. Он также позволяет переопределять любой блок, определенный внутри включенного шаблона, как это делает {layout}.

Для примера мы будем использовать элемент складного аккордеона. Давайте посмотрим на скелет элемента в шаблоне collapsible.latte:

<section class="collapsible {$modifierClass}">
	<h4 class="collapsible__title">
		{block title}{/block}
	</h4>

	<div class="collapsible__content">
		{block content}{/block}
	</div>
</section>

Теги {block} определяют два блока, которые могут заполнять дочерние шаблоны. Да, как и в случае с родительским шаблоном в шаблоне наследования макета. Вы также видите переменную $modifierClass.

Давайте используем наш элемент в шаблоне. Здесь на помощь приходит {embed}. Это супер мощный набор, который позволяет нам делать все: включать содержимое шаблона элемента, добавлять к нему переменные и добавлять к нему блоки с пользовательским HTML:

{embed 'collapsible.latte', modifierClass: my-style}
	{block title}
		Hello World
	{/block}

	{block content}
		<p>Lorem ipsum dolor sit amet, consectetuer adipiscing
		elit. Nunc dapibus tortor vel mi dapibus sollicitudin.</p>
	{/block}
{/embed}

Вывод может выглядеть следующим образом:

<section class="collapsible my-style">
	<h4 class="collapsible__title">
		Hello World
	</h4>

	<div class="collapsible__content">
		<p>Lorem ipsum dolor sit amet, consectetuer adipiscing
		elit. Nunc dapibus tortor vel mi dapibus sollicitudin.</p>
	</div>
</section>

Блоки внутри тегов embed образуют отдельный слой, независимый от других блоков. Поэтому они могут иметь то же имя, что и блок вне embed, и никак на них не влияют. Используя тег include внутри тегов {embed}, вы можете вставлять созданные здесь блоки, блоки из встроенного шаблона (которые не являются локальными), а также блоки из основного шаблона, которые являются локальными. Вы также можете импортировать блоки из других файлов:

{block outer}…{/block}
{block local hello}…{/block}

{embed 'collapsible.latte', modifierClass: my-style}
	{import 'blocks.latte'}

	{block inner}…{/block}

	{block title}
		{include inner} {* works, block is defined inside embed *}
		{include hello} {* works, block is local in this template *}
		{include content} {* works, block is defined in embedded template *}
		{include aBlockDefinedInImportedTemplate} {* works *}
		{include outer} {* does not work! - block is in outer layer *}
	{/block}
{/embed}

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

С помощью {embed} можно вставлять не только шаблоны, но и другие блоки, поэтому предыдущий пример можно написать так:

{define collapsible}
<section class="collapsible {$modifierClass}">
	<h4 class="collapsible__title">
		{block title}{/block}
	</h4>
	...
</section>
{/define}


{embed collapsible, modifierClass: my-style}
	{block title}
		Hello World
	{/block}
	...
{/embed}

Если мы передаем выражение в {embed} и не ясно, что это – блок или имя файла, добавьте ключевое слово block или file:

{embed block $name} ... {/embed}

Примеры использования

В Latte существуют различные виды наследования и повторного использования кода. Давайте обобщим основные понятия для большей наглядности:

{include template}

Use Case: Использование header.latte и footer.latte внутри layout.latte.

header.latte

<nav>
   <div>Home</div>
   <div>About</div>
</nav>

footer.latte

<footer>
   <div>Copyright</div>
</footer>

layout.latte

{include 'header.latte'}

<main>{block main}{/block}</main>

{include 'footer.latte'}

{layout}

Пример использования: Расширение layout.latte внутри homepage.latte и about.latte.

layout.latte

{include 'header.latte'}

<main>{block main}{/block}</main>

{include 'footer.latte'}

homepage.latte

{layout 'layout.latte'}

{block main}
	<p>Homepage</p>
{/block}

about.latte

{layout 'layout.latte'}

{block main}
	<p>About page</p>
{/block}

{import}

Пользовательский случай: sidebar.latte в single.product.latte и single.service.latte.

sidebar.latte

{block sidebar}<aside>This is sidebar</aside>{/block}

single.product.latte

{layout 'product.layout.latte'}

{import 'sidebar.latte'}

{block main}<main>Product page</main>{/block}

single.service.latte

{layout 'service.layout.latte'}

{import 'sidebar.latte'}

{block main}<main>Service page</main>{/block}

{define}

Пример использования: Функция, которая получает некоторые переменные и выводит некоторую разметку.

form.latte

{define form-input, $name, $value, $type = 'text'}
	<input type={$type} name={$name} value={$value}>
{/define}

profile.service.latte

{import 'form.latte'}

<form action="" method="post">
	<div>{include form-input, username}</div>
	<div>{include form-input, password}</div>
	<div>{include form-input, submit, Submit, submit}</div>
</form>

{embed}

Пример использования: Встраивание pagination.latte в product.table.latte и service.table.latte.

pagination.latte

<div id="pagination">
	<div>{block first}{/block}</div>

	{for $i = $min + 1; $i < $max - 1; $i++}
		<div>{$i}</div>
	{/for}

	<div>{block last}{/block}</div>
</div>

product.table.latte

{embed 'pagination.latte', min: 1, max: $products->count}
	{block first}First Product Page{/block}
	{block last}Last Product Page{/block}
{/embed}

service.table.latte

{embed 'pagination.latte', min: 1, max: $services->count}
	{block first}First Service Page{/block}
	{block last}Last Service Page{/block}
{/embed}