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-xxx、n:tag-xxx、n: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/application、nette/caching、nette/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
を通常どおり使っている場合、この拡張は自動的に設定されるので、何も変える必要はありません。
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 のやり方もほとんど同じで、MacroNode が
Latte\Compiler\Tag、PhpWriter が Latte\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;
}
}