Nette Documentation Preview

syntax
Latte 2 から 3 への移行
********************

.[perex]
Latte 3 はコンパイラが完全に書き直され、文法が形式的にきちんと定義されました。Latte 2 とできる限り一致するようになっていますが、いくつかの構文には少し手直しが必要です。

実際のところ、テンプレートの大多数は何も変更せずに、Latte 2 でも Latte 3 でも同じように動きます。では、非互換をどう見つければよいのでしょうか。

**まず、移行版の Latte 2.11 をインストールしてください。**

このバージョンは新機能を持たず、新しい Latte が対応しないと分かっているケースについて E_USER_DEPRECATED で警告し、さらに重要なことに、どう直せばよいかを教えてくれます。すべてのテンプレートを調べて互換性を確かめるには、コンソールから実行する [Linter |/develop#Linter] ツールが使えます。

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

考えられる非互換を解消したら、Latte 3.0 にアップグレードしてください。**そしてもう一度 Linter を実行し**、新しい厳格なパーサーがすべてのテンプレートを本当に理解できるか確かめましょう。


API の変更
=========

API の変更はカスタムタグの追加にだけ関わります。それ以外の API はバージョン 2 と同じで、テンプレートのレンダリング、パラメータの受け渡し、フィルタの登録の方法は変わりません。

例外は、いわゆる動的フィルタ `Engine::addFilter(null, ...)` です。これは現在、`addFilter()` メソッドを使う[クラスによって登録されるフィルタ |/custom-filters#Filters Using the Class]が担当します。もとの `Engine::addFilterLoader()` メソッドは移行のための手段として残っていますが、非推奨です。

カスタムタグを追加するための API はまったく異なるので、Latte 2 向けに作られたアドオンは動きません。[#アドオンの更新] も参照してください。


構文の変更
=========

変更点は次のとおりです。

- フィルタのパラメータ区切りにはカンマを使います。以前の `|filter: arg : arg` は `|filter: arg, arg` になります
- `{label foo}...{/label}` タグは常にペアです。ペアでない場合は `{label /}` と書きます
- 逆に `{_'text'}` タグは常に単独で、ペアの `{_}...{/}` は新しい `{translate}...{/translate}` に置き換わりました
- `{block foo-$var}` のような疑似文字列は引用符で `{block "foo-$var"}` と書くか、波かっこを足して `{block foo-{$var}}` と書く必要があります
- これは属性にも当てはまります。つまり `n:block="foo-$var"` ではなく `n:block="foo-{$var}"` を使います
- Latte 3 ではフィルタの大文字小文字を区別する必要があります
- `{do ...}` や `{php ...}` タグには式しか書けません。任意の PHP を使うには [RawPhpExtension |/develop#RawPhpExtension] を登録してください

さらに細かいケースもあります。

- `n:inner-xxx`、`n:tag-xxx`、`n:ifcontent` の属性は空要素の HTML 要素には使えません
- `n:inner-snippet` 属性は inner- を付けずに書かなければなりません
- `</script>` と `</style>` のタグは閉じなければなりません
- マジック変数 `$iterations` は削除されました(`$iterator` と混同しないでください)
- `{includeblock file.latte}` タグは [`{include file.latte with blocks}` |/tags#include] または [`{import}` |/template-inheritance#Horizontal Reuse] に置き換えてください
- `{include "abc"}` は、`"abc"` にピリオドが含まれていてファイルだと明らかな場合を除き、`{include file "abc"}` と書くべきです


アドオンの更新
==========

パーサーの全面的な書き直しにより、カスタムタグの書き方は完全に変わりました。Latte 用のカスタムタグを作っているなら、バージョン 3 向けに書き直す必要があります。[ドキュメント|/custom-tags]をご覧ください。

タグを追加する他者製のアドオンを使っている場合は、作者が Latte 3 向けのバージョンを出すのを待つ必要があります。バージョン 3.1 の `nette/application`、`nette/caching`、`nette/forms` ライブラリと Texy はすでに更新されており、Latte 2 と 3 の両方で動きます。


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

.[note]
Nette を通常どおり使っている場合、この拡張は自動的に設定されるので、何も変える必要はありません。

Latte 2 向けの古いコード:

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

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

Latte 3 向けの新しいコード:

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

UIExtension は `n:href`、`{link}`、`{control}`、`{snippet}` などを追加します。つまりスニペット用のタグは Latte 本体から `nette/application` ライブラリに移りました。Latte 3 では、プレゼンターの `templatePrepareFilters()` メソッドはもう呼ばれません。


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

.[note]
Nette を通常どおり使っている場合、この拡張は自動的に設定されるので、何も変える必要はありません。

Latte 2 向けの古いコード:

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

Latte 3 向けの新しいコード:

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


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

.[note]
Nette を通常どおり使っている場合、この拡張は自動的に設定されるので、何も変える必要はありません。

Latte 2 向けの古いコード:

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

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

Latte 3 向けの新しいコード:

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


Tracy
-----

Tracy 用のパネルも、今では拡張として有効にします。

Latte 2 向けの古いコード:

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

Latte 3 向けの新しいコード:

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


翻訳
---

TranslatorExtension は、翻訳タグ `{_'text'}`、新しいペアタグ `{translate}...{/translate}`、そして `|translate` フィルタを追加します。

Latte 2 向けの古いコード:

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

Latte 3 向けの新しいコード:

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

プレゼンターでは、`$template->setTranslator($translator)` メソッドでテンプレートにトランスレーターを設定すると自動的に有効になります。これがないと翻訳タグは使えないので、拡張を手動で、あるいは設定ファイルで登録する必要があります。


設定ファイル
=========

Latte 2 では、[設定ファイル |application:configuration#Latte テンプレート]の `latte › macros` セクションで新しいタグを登録できました。バージョン 3 では、この方法で拡張そのものを追加します。

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


Latte のアドオンを開発していますか?
==========================

ひとつのライブラリで Latte の両方のバージョンに同時に対応できます。バージョンの判定には `Latte\Engine::VERSION` 定数を使い、`onCompile[]` と `addMacro()` の利用を新しい `addExtension()` と分けるのがよいでしょう。

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

例として、Latte 2 向けの次のコードを Latte 3 向けに書き直してみましょう。

```php
// 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 は[拡張|/extending-latte]で拡張します。`foo` タグを追加するごく単純な拡張は次のようになります。

```php
// Latte 3 向けの新しいコード
class FooExtension extends Latte\Extension
{
	public function getTags(): array
	{
		return [
			'foo' => [FooNode::class, 'create'], // FooNode クラスはこのあと追加します
		];
	}
}

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

新しいコンパイラはより堅牢で、以前のような近道がないため、マクロを書くのに少し多くの行数がかかります。たとえば Latte 2 のように PHP コードの文字列を直接渡すことはできず、代わりに関数を作ります。Latte 2 では関数がこのような形だったことを思い出してください。

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

とはいえ Latte 3 のやり方もほとんど同じで、`MacroNode` が `Latte\Compiler\Tag`、`PhpWriter` が `Latte\Compiler\PrintContext` になっただけです。ただし何より重要なのは、中間の段階がひとつ増えたことです。関数は PHP コードを直接返すのではなく、ノード、つまり `StatementNode` の子を返し、それが AST ツリーの一部になります。そしてこのノードは、PHP コードを返す `print(Latte\Compiler\PrintContext $context): string` メソッドを持ちます。

```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 ...'); // PHP コードを返します
	}
}
```

さらに、`$context->format()` のマスクにはもう `%node.***` の略記がありません。先に[タグの内容を解析する |/custom-tags#タグの解析関数]ことが前提になっています。そこでパーサーを使って内容を変数(サブノード)に解析し、それから出力します。

```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;
		// タグの内容を解析します
		$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,
		);
	}
}
```

最後に、[走査 |/custom-tags#サブノードのための getIterator() の実装]のときにサブノードを辿れるよう、`getIterator()` メソッドを追加します。

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

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

{{priority: -1}}

Latte 2 から 3 への移行

Latte 3 はコンパイラが完全に書き直され、文法が形式的にきちんと定義されました。Latte 2 とできる限り一致するようになっていますが、いくつかの構文には少し手直しが必要です。

実際のところ、テンプレートの大多数は何も変更せずに、Latte 2 でも Latte 3 でも同じように動きます。では、非互換をどう見つければよいのでしょうか。

まず、移行版の Latte 2.11 をインストールしてください。

このバージョンは新機能を持たず、新しい Latte が対応しないと分かっているケースについて E_USER_DEPRECATED で警告し、さらに重要なことに、どう直せばよいかを教えてくれます。すべてのテンプレートを調べて互換性を確かめるには、コンソールから実行する Linter ツールが使えます。

vendor/bin/latte-lint <path>

考えられる非互換を解消したら、Latte 3.0 にアップグレードしてください。そしてもう一度 Linter を実行し、新しい厳格なパーサーがすべてのテンプレートを本当に理解できるか確かめましょう。

API の変更

API の変更はカスタムタグの追加にだけ関わります。それ以外の API はバージョン 2 と同じで、テンプレートのレンダリング、パラメータの受け渡し、フィルタの登録の方法は変わりません。

例外は、いわゆる動的フィルタ Engine::addFilter(null, ...) です。これは現在、addFilter() メソッドを使うクラスによって登録されるフィルタが担当します。もとの Engine::addFilterLoader() メソッドは移行のための手段として残っていますが、非推奨です。

カスタムタグを追加するための API はまったく異なるので、Latte 2 向けに作られたアドオンは動きません。アドオンの更新 も参照してください。

構文の変更

変更点は次のとおりです。

  • フィルタのパラメータ区切りにはカンマを使います。以前の |filter: arg : arg|filter: arg, arg になります
  • {label foo}...{/label} タグは常にペアです。ペアでない場合は {label /} と書きます
  • 逆に {_'text'} タグは常に単独で、ペアの {_}...{/} は新しい {translate}...{/translate} に置き換わりました
  • {block foo-$var} のような疑似文字列は引用符で {block "foo-$var"} と書くか、波かっこを足して {block foo-{$var}} と書く必要があります
  • これは属性にも当てはまります。つまり n:block="foo-$var" ではなく n:block="foo-{$var}" を使います
  • Latte 3 ではフィルタの大文字小文字を区別する必要があります
  • {do ...}{php ...} タグには式しか書けません。任意の PHP を使うには RawPhpExtension を登録してください

さらに細かいケースもあります。

  • n:inner-xxxn:tag-xxxn:ifcontent の属性は空要素の HTML 要素には使えません
  • n:inner-snippet 属性は inner- を付けずに書かなければなりません
  • </script></style> のタグは閉じなければなりません
  • マジック変数 $iterations は削除されました($iterator と混同しないでください)
  • {includeblock file.latte} タグは {include file.latte with blocks} または {import} に置き換えてください
  • {include "abc"} は、"abc" にピリオドが含まれていてファイルだと明らかな場合を除き、{include file "abc"} と書くべきです

アドオンの更新

パーサーの全面的な書き直しにより、カスタムタグの書き方は完全に変わりました。Latte 用のカスタムタグを作っているなら、バージョン 3 向けに書き直す必要があります。ドキュメントをご覧ください。

タグを追加する他者製のアドオンを使っている場合は、作者が Latte 3 向けのバージョンを出すのを待つ必要があります。バージョン 3.1 の nette/applicationnette/cachingnette/forms ライブラリと Texy はすでに更新されており、Latte 2 と 3 の両方で動きます。

nette/application

Nette を通常どおり使っている場合、この拡張は自動的に設定されるので、何も変える必要はありません。

Latte 2 向けの古いコード:

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

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

Latte 3 向けの新しいコード:

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

UIExtension は n:href{link}{control}{snippet} などを追加します。つまりスニペット用のタグは Latte 本体から nette/application ライブラリに移りました。Latte 3 では、プレゼンターの templatePrepareFilters() メソッドはもう呼ばれません。

nette/forms

Nette を通常どおり使っている場合、この拡張は自動的に設定されるので、何も変える必要はありません。

Latte 2 向けの古いコード:

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

Latte 3 向けの新しいコード:

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

nette/caching

Nette を通常どおり使っている場合、この拡張は自動的に設定されるので、何も変える必要はありません。

Latte 2 向けの古いコード:

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

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

Latte 3 向けの新しいコード:

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

Tracy

Tracy 用のパネルも、今では拡張として有効にします。

Latte 2 向けの古いコード:

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

Latte 3 向けの新しいコード:

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

翻訳

TranslatorExtension は、翻訳タグ {_'text'}、新しいペアタグ {translate}...{/translate}、そして |translate フィルタを追加します。

Latte 2 向けの古いコード:

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

Latte 3 向けの新しいコード:

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

プレゼンターでは、$template->setTranslator($translator) メソッドでテンプレートにトランスレーターを設定すると自動的に有効になります。これがないと翻訳タグは使えないので、拡張を手動で、あるいは設定ファイルで登録する必要があります。

設定ファイル

Latte 2 では、設定ファイルlatte › macros セクションで新しいタグを登録できました。バージョン 3 では、この方法で拡張そのものを追加します。

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

Latte のアドオンを開発していますか?

ひとつのライブラリで Latte の両方のバージョンに同時に対応できます。バージョンの判定には Latte\Engine::VERSION 定数を使い、onCompile[]addMacro() の利用を新しい addExtension() と分けるのがよいでしょう。

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

例として、Latte 2 向けの次のコードを Latte 3 向けに書き直してみましょう。

// 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 は拡張で拡張します。foo タグを追加するごく単純な拡張は次のようになります。

// Latte 3 向けの新しいコード
class FooExtension extends Latte\Extension
{
	public function getTags(): array
	{
		return [
			'foo' => [FooNode::class, 'create'], // FooNode クラスはこのあと追加します
		];
	}
}

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

新しいコンパイラはより堅牢で、以前のような近道がないため、マクロを書くのに少し多くの行数がかかります。たとえば Latte 2 のように PHP コードの文字列を直接渡すことはできず、代わりに関数を作ります。Latte 2 では関数がこのような形だったことを思い出してください。

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

とはいえ Latte 3 のやり方もほとんど同じで、MacroNodeLatte\Compiler\TagPhpWriterLatte\Compiler\PrintContext になっただけです。ただし何より重要なのは、中間の段階がひとつ増えたことです。関数は PHP コードを直接返すのではなく、ノード、つまり StatementNode の子を返し、それが AST ツリーの一部になります。そしてこのノードは、PHP コードを返す print(Latte\Compiler\PrintContext $context): string メソッドを持ちます。

// 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 ...'); // PHP コードを返します
	}
}

さらに、$context->format() のマスクにはもう %node.*** の略記がありません。先にタグの内容を解析することが前提になっています。そこでパーサーを使って内容を変数(サブノード)に解析し、それから出力します。

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;
		// タグの内容を解析します
		$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,
		);
	}
}

最後に、走査のときにサブノードを辿れるよう、getIterator() メソッドを追加します。

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

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