Nette Documentation Preview

syntax
Migracja z Latte 2 do 3
***********************

.[perex]
Latte 3 ma całkowicie przepisany kompilator i formalnie dobrze zdefiniowaną gramatykę. Powinna ona odpowiadać Latte 2 tak wiernie, jak to możliwe, ale istnieje kilka konstrukcji, które wymagają drobnych poprawek.

W praktyce okazuje się, że zdecydowana większość szablonów nie wymaga żadnych zmian i działa w Latte 3 tak samo jak w Latte 2. Jak jednak wykryć niezgodności?

**Najpierw zainstaluj wersję przejściową Latte 2.11.**

Ta wersja nie wnosi żadnych nowych funkcji, tylko za pomocą E_USER_DEPRECATED ostrzega przed przypadkami, o których wie, że nowe Latte ich nie obsłuży, a co ważniejsze, podpowiada, jak je poprawić. Aby przejrzeć wszystkie szablony i sprawdzić, czy są zgodne, możesz użyć narzędzia [Linter |/develop#Linter], które uruchamiasz z konsoli:

```shell
vendor/bin/latte-lint <path>
```

Gdy rozwiążesz ewentualne niezgodności, przejdź na Latte 3.0. **I uruchom Linter ponownie**, aby upewnić się, że nowy, ścisły parser naprawdę rozumie wszystkie szablony.


Zmiany w API
============

Zmiany w API dotyczą tylko dodawania własnych tagów. Reszta API pozostaje taka sama jak w wersji 2, czyli ten sam sposób renderowania szablonów, przekazywania parametrów i rejestrowania filtrów.

Wyjątkiem jest tzw. dynamiczny filtr `Engine::addFilter(null, ...)`, którym teraz zajmują się [filtry rejestrowane przez klasę |/custom-filters#Filtry używające klasy] za pomocą metody `addFilter()`. Pierwotna metoda `Engine::addFilterLoader()` nadal istnieje jako rozwiązanie przejściowe, ale jest przestarzała.

API do dodawania własnych tagów jest całkowicie inne, więc dodatki napisane dla Latte 2 nie będą z nim działać. Zobacz też [#Aktualizacja dodatków].


Zmiany w składni
================

Zmiany są następujące:

- filtry używają przecinka jako separatora parametrów, dotychczasowe `|filter: arg : arg` to teraz `|filter: arg, arg`
- tag `{label foo}...{/label}` jest zawsze parzysty, nieparzysty należy zapisywać `{label /}`
- odwrotnie, tag `{_'text'}` jest zawsze nieparzysty, parzysty `{_}...{/}` zastępuje nowy `{translate}...{/translate}`
- pseudołańcuchy takie jak `{block foo-$var}` trzeba zapisywać w cudzysłowach `{block "foo-$var"}` albo dodać klamry `{block foo-{$var}}`
- dotyczy to również atrybutów, czyli zamiast `n:block="foo-$var"` użyj `n:block="foo-{$var}"`.
- w Latte 3 trzeba zwracać uwagę na wielkość liter w nazwach filtrów
- Tag `{do ...}` lub `{php ...}` może zawierać wyłącznie wyrażenia; aby użyć dowolnego kodu PHP, zarejestruj [RawPhpExtension |/develop#RawPhpExtension].

I dalsze przypadki brzegowe:

- atrybutów `n:inner-xxx`, `n:tag-xxx` i `n:ifcontent` nie można używać na pustych elementach HTML
- atrybut `n:inner-snippet` trzeba zapisywać bez inner-
- tagi `</script>` i `</style>` muszą być zamknięte
- magiczna zmienna `$iterations` została usunięta (nie mylić z `$iterator`!)
- tag `{includeblock file.latte}` zastąp przez [`{include file.latte with blocks}` |/tags#include] albo [`{import}` |/template-inheritance#Poziome wykorzystanie]
- `{include "abc"}` należy zapisywać jako `{include file "abc"}`, chyba że `"abc"` zawiera kropkę i widać, że chodzi o plik


Aktualizacja dodatków
=====================

Wraz z całkowitym przepisaniem parsera zupełnie zmienił się sposób pisania własnych tagów. Jeśli masz własne tagi napisane dla Latte, będziesz musiał napisać je na nowo dla wersji 3, zobacz [dokumentację|/custom-tags].

Jeśli używasz obcego dodatku, który dodaje tagi, będziesz musiał poczekać, aż autor wyda wersję dla Latte 3. Biblioteki `nette/application`, `nette/caching` i `nette/forms` w wersji 3.1, a także Texy, zostały już zaktualizowane i działają zarówno z Latte 2, jak i 3.


nette/application
-----------------

.[note]
Przy normalnym używaniu Nette to rozszerzenie jest ustawiane automatycznie i nie trzeba nic zmieniać.

Stary kod dla Latte 2:

```php
$latte->onCompile[] = function ($latte) {
	Nette\Bridges\ApplicationLatte\UIMacros::install($latte->getCompiler());
};

$latte->addProvider('uiControl', $control);
$latte->addProvider('uiPresenter', $control->getPresenter());
```

Nowy kod dla Latte 3:

```php
$latte->addExtension(new Nette\Bridges\ApplicationLatte\UIExtension($control));
```

UIExtension dodaje `n:href`, `{link}`, `{control}`, `{snippet}` itd. Tagi do snippetów przenoszą się więc z samego Latte do biblioteki `nette/application`. W Latte 3 metoda presentera `templatePrepareFilters()` nie jest już wywoływana.


nette/forms
-----------

.[note]
Przy normalnym używaniu Nette to rozszerzenie jest ustawiane automatycznie i nie trzeba nic zmieniać.

Stary kod dla Latte 2:

```php
$latte->onCompile[] = function ($latte) {
	Nette\Bridges\FormsLatte\FormMacros::install($latte->getCompiler());
};
```

Nowy kod dla Latte 3:

```php
$latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension);
```


nette/caching
-------------

.[note]
Przy normalnym używaniu Nette to rozszerzenie jest ustawiane automatycznie i nie trzeba nic zmieniać.

Stary kod dla Latte 2:

```php
$latte->onCompile[] = function ($latte) {
	$latte->getCompiler()->addMacro('cache', new Nette\Bridges\CacheLatte\CacheMacro);
};

$latte->addProvider('cacheStorage', $cacheStorage);
```

Nowy kod dla Latte 3:

```php
$latte->addExtension(new Nette\Bridges\CacheLatte\CacheExtension($cacheStorage));
```


Tracy
-----

Panel dla Tracy jest teraz również aktywowany jako rozszerzenie.

Stary kod dla Latte 2:

```php
$latte = new Latte\Engine;
Latte\Bridges\Tracy\LattePanel::initialize($latte);
```

Nowy kod dla Latte 3:

```php
$latte = new Latte\Engine;
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);
```


Tłumaczenia
-----------

TranslatorExtension dodaje tagi tłumaczeń `{_'text'}`, nowy parzysty `{translate}...{/translate}` oraz filtr `|translate`.

Stary kod dla Latte 2:

```php
$latte->addFilter('translate', [$translator, 'translate']);
```

Nowy kod dla Latte 3:

```php
$latte->addExtension(new Latte\Essential\TranslatorExtension($translator));
```

W presenterach jest aktywowane automatycznie przez ustawienie translatora w szablonie metodą `$template->setTranslator($translator)`. Bez tego tagi tłumaczeń nie będą dostępne i trzeba zarejestrować rozszerzenie ręcznie albo w pliku konfiguracyjnym.


Plik konfiguracyjny
===================

W Latte 2 można było rejestrować nowe tagi w [pliku konfiguracyjnym |application:configuration#Szablony Latte] w sekcji `latte › macros`. W wersji 3 dodaje się w ten sposób całe rozszerzenia:

```neon
latte:
	extensions:
		- App\Templating\LatteExtension
		- Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
```


Tworzysz dodatek do Latte?
==========================

W swojej bibliotece możesz jednocześnie wspierać obie wersje Latte. Do wykrycia wersji najlepiej użyć stałej `Latte\Engine::VERSION` i rozdzielić użycie `onCompile[]` i `addMacro()` od nowego `addExtension()`:

```php
if (version_compare(Latte\Engine::VERSION, '3', '<')) {
	// inicjalizacja Latte 2
	$this->latte->onCompile[] = function ($latte) {
		$latte->addMacro(/* ... */);
	};
} else {
	// inicjalizacja Latte 3
	$this->latte->addExtension(/* ... */);
}
```

Jako przykład spróbujmy przepisać następujący kod przeznaczony dla Latte 2 do postaci dla Latte 3:

```php
// stary kod dla Latte 2
$this->latte->onCompile[] = function (Latte\Engine $latte) {
	$set = new Latte\Macros\MacroSet($latte->getCompiler());
	$set->addMacro('foo', 'echo %escape(MyClass:myFunc(%node.word, %node.array))');
};
```

Latte 3 rozszerza się za pomocą [rozszerzeń|/extending-latte]. Trywialne rozszerzenie dodające tag `foo` wyglądałoby tak:

```php
// nowy kod dla Latte 3
class FooExtension extends Latte\Extension
{
	public function getTags(): array
	{
		return [
			'foo' => [FooNode::class, 'create'], // klasę FooNode dodamy za chwilę
		];
	}
}

// rejestracja
$this->latte->addExtension(new FooExtension);
```

Nowy kompilator jest solidniejszy, nie zawiera dotychczasowych skrótów, więc napisanie makra zajmuje nieco więcej wierszy kodu. Na przykład nie możemy bezpośrednio przekazać łańcucha z kodem PHP jak w Latte 2, zamiast tego tworzymy funkcję. Przypomnijmy, że w Latte 2 funkcja wyglądałaby mniej więcej tak:

```php
// Latte 2
$set->addMacro('foo', function (Latte\MacroNode $node, Latte\PhpWriter $writer) {
	return $writer->write('echo %escape(MyClass:myFunc(%node.word, %node.array))');
});
```

Mimo to Latte 3 postępuje bardzo podobnie, tylko `MacroNode` nazywa się `Latte\Compiler\Tag`, a `PhpWriter` to `Latte\Compiler\PrintContext`. Co jednak najważniejsze, dochodzi jeden krok pośredni: funkcja nie zwraca kodu PHP bezpośrednio, lecz zwraca węzeł, czyli potomka `StatementNode`, który następnie jest częścią drzewa AST. A ten węzeł ma metodę `print(Latte\Compiler\PrintContext $context): string`, która zwraca kod PHP:

```php
// Latte 3
class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	public static function create(Latte\Compiler\Tag $tag): self
	{
		$node = new self;
		return $node;
	}

	public function print(Latte\Compiler\PrintContext $context): string
	{
		return $context->format('echo ...'); // zwraca kod PHP
	}
}
```

Ponadto maska w `$context->format()` nie ma już skrótów `%node.***`, zakłada się, że najpierw [sparsujesz zawartość tagu |/custom-tags#Funkcja parsująca tag]. Używamy więc parsera, aby rozłożyć zawartość na zmienne (podwęzły), a potem ją wypisujemy:

```php
use Latte\Compiler\Nodes\Php\Expression\ArrayNode;
use Latte\Compiler\Nodes\Php\ExpressionNode;

class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	public ExpressionNode $subject;
	public ArrayNode $args;

	public static function create(Latte\Compiler\Tag $tag): self
	{
		$node = new self;
		// parsowanie zawartości tagu
		$node->subject = $tag->parser->parseUnquotedStringOrExpression();
		$tag->parser->stream->tryConsume(',');
		$node->args = $tag->parser->parseArguments();
		return $node;
	}

	public function print(Latte\Compiler\PrintContext $context): string
	{
		return $context->format(
			'echo %escape(MyClass:myFunc(%node, %node));',
			$this->subject,
			$this->args,
		);
	}
}
```

Na koniec dodamy metodę `getIterator()`, aby umożliwić przechodzenie po podwęzłach przy [trawersowaniu |/custom-tags#Implementacja getIterator() dla podwęzłów]:

```php
class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	...

	public function &getIterator(): \Generator
	{
		yield $this->subject;
		yield $this->args;
	}
}
```

{{priority: -1}}

Migracja z Latte 2 do 3

Latte 3 ma całkowicie przepisany kompilator i formalnie dobrze zdefiniowaną gramatykę. Powinna ona odpowiadać Latte 2 tak wiernie, jak to możliwe, ale istnieje kilka konstrukcji, które wymagają drobnych poprawek.

W praktyce okazuje się, że zdecydowana większość szablonów nie wymaga żadnych zmian i działa w Latte 3 tak samo jak w Latte 2. Jak jednak wykryć niezgodności?

Najpierw zainstaluj wersję przejściową Latte 2.11.

Ta wersja nie wnosi żadnych nowych funkcji, tylko za pomocą E_USER_DEPRECATED ostrzega przed przypadkami, o których wie, że nowe Latte ich nie obsłuży, a co ważniejsze, podpowiada, jak je poprawić. Aby przejrzeć wszystkie szablony i sprawdzić, czy są zgodne, możesz użyć narzędzia Linter, które uruchamiasz z konsoli:

vendor/bin/latte-lint <path>

Gdy rozwiążesz ewentualne niezgodności, przejdź na Latte 3.0. I uruchom Linter ponownie, aby upewnić się, że nowy, ścisły parser naprawdę rozumie wszystkie szablony.

Zmiany w API

Zmiany w API dotyczą tylko dodawania własnych tagów. Reszta API pozostaje taka sama jak w wersji 2, czyli ten sam sposób renderowania szablonów, przekazywania parametrów i rejestrowania filtrów.

Wyjątkiem jest tzw. dynamiczny filtr Engine::addFilter(null, ...), którym teraz zajmują się filtry rejestrowane przez klasę za pomocą metody addFilter(). Pierwotna metoda Engine::addFilterLoader() nadal istnieje jako rozwiązanie przejściowe, ale jest przestarzała.

API do dodawania własnych tagów jest całkowicie inne, więc dodatki napisane dla Latte 2 nie będą z nim działać. Zobacz też Aktualizacja dodatków.

Zmiany w składni

Zmiany są następujące:

  • filtry używają przecinka jako separatora parametrów, dotychczasowe |filter: arg : arg to teraz |filter: arg, arg
  • tag {label foo}...{/label} jest zawsze parzysty, nieparzysty należy zapisywać {label /}
  • odwrotnie, tag {_'text'} jest zawsze nieparzysty, parzysty {_}...{/} zastępuje nowy {translate}...{/translate}
  • pseudołańcuchy takie jak {block foo-$var} trzeba zapisywać w cudzysłowach {block "foo-$var"} albo dodać klamry {block foo-{$var}}
  • dotyczy to również atrybutów, czyli zamiast n:block="foo-$var" użyj n:block="foo-{$var}".
  • w Latte 3 trzeba zwracać uwagę na wielkość liter w nazwach filtrów
  • Tag {do ...} lub {php ...} może zawierać wyłącznie wyrażenia; aby użyć dowolnego kodu PHP, zarejestruj RawPhpExtension.

I dalsze przypadki brzegowe:

  • atrybutów n:inner-xxx, n:tag-xxx i n:ifcontent nie można używać na pustych elementach HTML
  • atrybut n:inner-snippet trzeba zapisywać bez inner-
  • tagi </script> i </style> muszą być zamknięte
  • magiczna zmienna $iterations została usunięta (nie mylić z $iterator!)
  • tag {includeblock file.latte} zastąp przez {include file.latte with blocks} albo {import}
  • {include "abc"} należy zapisywać jako {include file "abc"}, chyba że "abc" zawiera kropkę i widać, że chodzi o plik

Aktualizacja dodatków

Wraz z całkowitym przepisaniem parsera zupełnie zmienił się sposób pisania własnych tagów. Jeśli masz własne tagi napisane dla Latte, będziesz musiał napisać je na nowo dla wersji 3, zobacz dokumentację.

Jeśli używasz obcego dodatku, który dodaje tagi, będziesz musiał poczekać, aż autor wyda wersję dla Latte 3. Biblioteki nette/application, nette/caching i nette/forms w wersji 3.1, a także Texy, zostały już zaktualizowane i działają zarówno z Latte 2, jak i 3.

nette/application

Przy normalnym używaniu Nette to rozszerzenie jest ustawiane automatycznie i nie trzeba nic zmieniać.

Stary kod dla Latte 2:

$latte->onCompile[] = function ($latte) {
	Nette\Bridges\ApplicationLatte\UIMacros::install($latte->getCompiler());
};

$latte->addProvider('uiControl', $control);
$latte->addProvider('uiPresenter', $control->getPresenter());

Nowy kod dla Latte 3:

$latte->addExtension(new Nette\Bridges\ApplicationLatte\UIExtension($control));

UIExtension dodaje n:href, {link}, {control}, {snippet} itd. Tagi do snippetów przenoszą się więc z samego Latte do biblioteki nette/application. W Latte 3 metoda presentera templatePrepareFilters() nie jest już wywoływana.

nette/forms

Przy normalnym używaniu Nette to rozszerzenie jest ustawiane automatycznie i nie trzeba nic zmieniać.

Stary kod dla Latte 2:

$latte->onCompile[] = function ($latte) {
	Nette\Bridges\FormsLatte\FormMacros::install($latte->getCompiler());
};

Nowy kod dla Latte 3:

$latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension);

nette/caching

Przy normalnym używaniu Nette to rozszerzenie jest ustawiane automatycznie i nie trzeba nic zmieniać.

Stary kod dla Latte 2:

$latte->onCompile[] = function ($latte) {
	$latte->getCompiler()->addMacro('cache', new Nette\Bridges\CacheLatte\CacheMacro);
};

$latte->addProvider('cacheStorage', $cacheStorage);

Nowy kod dla Latte 3:

$latte->addExtension(new Nette\Bridges\CacheLatte\CacheExtension($cacheStorage));

Tracy

Panel dla Tracy jest teraz również aktywowany jako rozszerzenie.

Stary kod dla Latte 2:

$latte = new Latte\Engine;
Latte\Bridges\Tracy\LattePanel::initialize($latte);

Nowy kod dla Latte 3:

$latte = new Latte\Engine;
$latte->addExtension(new Latte\Bridges\Tracy\TracyExtension);

Tłumaczenia

TranslatorExtension dodaje tagi tłumaczeń {_'text'}, nowy parzysty {translate}...{/translate} oraz filtr |translate.

Stary kod dla Latte 2:

$latte->addFilter('translate', [$translator, 'translate']);

Nowy kod dla Latte 3:

$latte->addExtension(new Latte\Essential\TranslatorExtension($translator));

W presenterach jest aktywowane automatycznie przez ustawienie translatora w szablonie metodą $template->setTranslator($translator). Bez tego tagi tłumaczeń nie będą dostępne i trzeba zarejestrować rozszerzenie ręcznie albo w pliku konfiguracyjnym.

Plik konfiguracyjny

W Latte 2 można było rejestrować nowe tagi w pliku konfiguracyjnym w sekcji latte › macros. W wersji 3 dodaje się w ten sposób całe rozszerzenia:

latte:
	extensions:
		- App\Templating\LatteExtension
		- Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)

Tworzysz dodatek do Latte?

W swojej bibliotece możesz jednocześnie wspierać obie wersje Latte. Do wykrycia wersji najlepiej użyć stałej Latte\Engine::VERSION i rozdzielić użycie onCompile[] i addMacro() od nowego addExtension():

if (version_compare(Latte\Engine::VERSION, '3', '<')) {
	// inicjalizacja Latte 2
	$this->latte->onCompile[] = function ($latte) {
		$latte->addMacro(/* ... */);
	};
} else {
	// inicjalizacja Latte 3
	$this->latte->addExtension(/* ... */);
}

Jako przykład spróbujmy przepisać następujący kod przeznaczony dla Latte 2 do postaci dla Latte 3:

// stary kod dla Latte 2
$this->latte->onCompile[] = function (Latte\Engine $latte) {
	$set = new Latte\Macros\MacroSet($latte->getCompiler());
	$set->addMacro('foo', 'echo %escape(MyClass:myFunc(%node.word, %node.array))');
};

Latte 3 rozszerza się za pomocą rozszerzeń. Trywialne rozszerzenie dodające tag foo wyglądałoby tak:

// nowy kod dla Latte 3
class FooExtension extends Latte\Extension
{
	public function getTags(): array
	{
		return [
			'foo' => [FooNode::class, 'create'], // klasę FooNode dodamy za chwilę
		];
	}
}

// rejestracja
$this->latte->addExtension(new FooExtension);

Nowy kompilator jest solidniejszy, nie zawiera dotychczasowych skrótów, więc napisanie makra zajmuje nieco więcej wierszy kodu. Na przykład nie możemy bezpośrednio przekazać łańcucha z kodem PHP jak w Latte 2, zamiast tego tworzymy funkcję. Przypomnijmy, że w Latte 2 funkcja wyglądałaby mniej więcej tak:

// Latte 2
$set->addMacro('foo', function (Latte\MacroNode $node, Latte\PhpWriter $writer) {
	return $writer->write('echo %escape(MyClass:myFunc(%node.word, %node.array))');
});

Mimo to Latte 3 postępuje bardzo podobnie, tylko MacroNode nazywa się Latte\Compiler\Tag, a PhpWriter to Latte\Compiler\PrintContext. Co jednak najważniejsze, dochodzi jeden krok pośredni: funkcja nie zwraca kodu PHP bezpośrednio, lecz zwraca węzeł, czyli potomka StatementNode, który następnie jest częścią drzewa AST. A ten węzeł ma metodę print(Latte\Compiler\PrintContext $context): string, która zwraca kod PHP:

// Latte 3
class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	public static function create(Latte\Compiler\Tag $tag): self
	{
		$node = new self;
		return $node;
	}

	public function print(Latte\Compiler\PrintContext $context): string
	{
		return $context->format('echo ...'); // zwraca kod PHP
	}
}

Ponadto maska w $context->format() nie ma już skrótów %node.***, zakłada się, że najpierw sparsujesz zawartość tagu. Używamy więc parsera, aby rozłożyć zawartość na zmienne (podwęzły), a potem ją wypisujemy:

use Latte\Compiler\Nodes\Php\Expression\ArrayNode;
use Latte\Compiler\Nodes\Php\ExpressionNode;

class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	public ExpressionNode $subject;
	public ArrayNode $args;

	public static function create(Latte\Compiler\Tag $tag): self
	{
		$node = new self;
		// parsowanie zawartości tagu
		$node->subject = $tag->parser->parseUnquotedStringOrExpression();
		$tag->parser->stream->tryConsume(',');
		$node->args = $tag->parser->parseArguments();
		return $node;
	}

	public function print(Latte\Compiler\PrintContext $context): string
	{
		return $context->format(
			'echo %escape(MyClass:myFunc(%node, %node));',
			$this->subject,
			$this->args,
		);
	}
}

Na koniec dodamy metodę getIterator(), aby umożliwić przechodzenie po podwęzłach przy trawersowaniu:

class FooNode extends Latte\Compiler\Nodes\StatementNode
{
	...

	public function &getIterator(): \Generator
	{
		yield $this->subject;
		yield $this->args;
	}
}