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/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 のやり方もほとんど同じで、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;
}
}