Latte の拡張
Latte は拡張しやすさを念頭に設計されています。標準のタグ、フィルタ、関数のセットは多くの用途をカバーしますが、独自のロジックやヘルパーを追加したくなることもよくあります。このページでは、単純なヘルパーから複雑な新しい構文まで、プロジェクトの要件にぴったり合うように Latte を拡張する方法を概観します。
Latte を拡張する方法
Latte をカスタマイズ・拡張する主な方法をざっと見てみましょう。
- カスタムフィルタ:
テンプレートの出力を直接整形・変換するためのもの(
{$var|myFilter}など)。日付の書式づけ、テキストの加工、特定のエスケープの適用といった用途に最適です。内容を無名の{block}で包んでカスタムフィルタを適用すれば、大きな HTML の塊を加工することもできます。 - カスタム関数:
テンプレートの式の中から呼べる再利用可能なロジックを追加するためのもの(
{myFunction($arg1, $arg2)}など)。計算、アプリケーションのヘルパーへのアクセス、小さなコンテンツの生成に便利です。 - カスタムタグ:
まったく新しい言語構造(
{mytag}...{/mytag}やn:mytag)を作るためのもの。タグは最も強力で、独自の構造の定義、テンプレート解析の制御、複雑なレンダリングロジックの実装ができます。 - コンパイラパス: 解析後・PHP コード生成前に、テンプレートの抽象構文木(AST)を書き換える関数。高度な最適化、セキュリティチェック(サンドボックスなど)、コードの自動変更に使います。
- カスタムローダー: Latte がテンプレートファイルを見つけて読み込む方法を変えるためのもの(データベースや暗号化されたストレージからの読み込みなど)。
適切な拡張方法を選ぶことが肝心です。複雑なタグを作る前に、もっと単純なフィルタや関数で足りないか考えてみてください。例で見てみましょう。生成する単語数を引数に取る Lorem ipsum ジェネレーターを実装するとします。
- タグとして?
{lipsum 40}– 可能ですが、タグは制御構造や複雑なマークアップの生成に向いています。タグは式の中で直接使えません。 - フィルタとして?
{=40|lipsum}– 技術的には動きますが、フィルタは入力を変換するためのものです。ここでの40は変換される値ではなく引数です。意味的にしっくりきません。 - 関数として?
{lipsum(40)}– これが最も自然です。関数は引数を受け取って値を返すので、{var $text = lipsum(40)}のようにどんな式の中でも使えます。
一般的な指針: 計算や生成には関数を、変換にはフィルタを、新しい言語構造や複雑なマークアップにはタグを使いましょう。AST の操作にはパスを、テンプレートの取得にはローダーを使います。
直接登録
プロジェクト固有のヘルパーや手早い追加のために、Latte は Latte\Engine
オブジェクトへフィルタや関数を直接登録できます。
フィルタの登録には addFilter() を使います。フィルタ関数の最初の引数は |
パイプの前の値で、それ以降の引数は : コロンのあとに渡された値です。
$latte = new Latte\Engine;
// フィルタの定義(callable: 関数、静的メソッドなど)
$myTruncate = fn(string $s, int $length = 50) => mb_substr($s, 0, $length);
// 登録します
$latte->addFilter('truncate', $myTruncate);
// テンプレートでの使い方: {$text|truncate} または {$text|truncate:100}
テンプレートの式の中で使える関数の登録には addFunction() を使います。
$latte = new Latte\Engine;
// 関数の定義
$isWeekend = fn(DateTimeInterface $date) => $date->format('N') >= 6;
// 登録します
$latte->addFunction('isWeekend', $isWeekend);
// テンプレートでの使い方: {if isWeekend($myDate)}Weekend!{/if}
詳しくはカスタムフィルタの作成と関数をご覧ください。
堅実な方法: Latte Extension
直接登録は簡単ですが、Latte のカスタマイズをまとめて配布する標準的で推奨される方法は Extension クラスです。Extension は、複数のタグ、フィルタ、関数、コンパイラパスなどを登録する中心的な設定点として働きます。
なぜ Extension を使うのでしょうか。
- 整理: 関連するカスタマイズ(ある機能のためのタグ、フィルタなど)をひとつのクラスにまとめられます。
- 再利用と共有: 拡張をパッケージにして、ほかのプロジェクトで使ったりコミュニティで共有したり(Composer 経由など)しやすくなります。
- 完全な力: カスタムタグとコンパイラパスは Extension 経由でしか登録できません。
Extension の登録
Extension は addExtension()(または設定ファイル)で Latte
に登録します。
$latte = new Latte\Engine;
$latte->addExtension(new MyProjectExtension);
複数の拡張を登録し、それらが同じ名前のタグ、フィルタ、関数を定義している場合は、最後に追加した拡張が勝ちます。つまり、あなたの拡張はネイティブのタグ・フィルタ・関数を上書きできます。
クラスに変更を加えたとき、自動リフレッシュが無効になっていなければ、Latte はテンプレートを自動的に再コンパイルします。
Extension の作成
独自の拡張を作るには、Latte\Extension を継承したクラスを作ります。拡張がどんなものかつかむには、組み込みの CoreExtension を眺めてみてください。
実装できるメソッドを見ていきましょう。
beforeCompile (Latte\Engine $engine): void
テンプレートがコンパイルされる前に呼ばれます。たとえばコンパイルに関する初期化に使えます。
getTags(): array
テンプレートのコンパイル時に呼ばれます。*タグ名 ⇒ callable* の連想配列を返します。callable はタグの解析関数です。詳しくはこちら。
public function getTags(): array
{
return [
'foo' => FooNode::create(...),
'bar' => BarNode::create(...),
'n:baz' => NBazNode::create(...),
// ...
];
}
n:baz タグは純粋な n:属性、つまり属性としてしか書けないタグを表します。
foo と bar のタグについては、Latte
がペアかどうかを自動的に判断し、ペアであれば n:inner-foo や n:tag-foo
の接頭辞付きの形も含め、n:属性として自動的に書けるようになります。
こうした n:属性の実行順序は、getTags()
が返す配列の中での順序で決まります。したがって、HTML タグに
<div n:bar="..." n:foo="..."> と逆順で書かれていても、n:foo は常に
n:bar より先に実行されます。
複数の拡張にまたがって n:属性の順序を決める必要がある場合は、order()
ヘルパーメソッドを使います。before や after
パラメータで、どのタグより前・あとに並べるかを指定します。
public function getTags(): array
{
return [
'foo' => self::order(FooNode::create(...), before: 'bar'),
'bar' => self::order(BarNode::create(...), after: ['block', 'snippet']),
];
}
getPasses(): array
テンプレートのコンパイル時に呼ばれます。*パス名 ⇒ callable* の連想配列を返します。callable は AST を走査して書き換える、いわゆるコンパイラパスを表す関数です。
ここでも order() ヘルパーメソッドが使えます。before や after
パラメータの値には * を指定でき、「すべての前・あと」を意味します。
public function getPasses(): array
{
return [
'optimize' => Passes::optimizePass(...),
'sandbox' => self::order($this->sandboxPass(...), before: '*'),
// ...
];
}
beforeRender (Latte\Runtime\Template $template): void
テンプレートがレンダリングされるたび、その前に呼ばれます。たとえばレンダリング中に使う変数の初期化に使えます。
afterRender (Latte\Runtime\Template $template): void
テンプレートがレンダリングされるたび、そのあとに呼ばれます。{exitIf}
で早く終わった場合や例外で中断された場合にも実行されるので、後始末や計測にちょうどよい場所です。
getFilters(): array
addExtension() メソッドで拡張が登録されるときに呼ばれます。*フィルタ名 ⇒ callable*
の連想配列としてフィルタを返します。詳しくはこちら。
public function getFilters(): array
{
return [
'batch' => $this->batchFilter(...),
'trim' => $this->trimFilter(...),
// ...
];
}
getFunctions(): array
addExtension() メソッドで拡張が登録されるときに呼ばれます。*関数名 ⇒ callable*
の連想配列として関数を返します。詳しくはこちら。
public function getFunctions(): array
{
return [
'clamp' => $this->clampFunction(...),
'divisibleBy' => $this->divisibleByFunction(...),
// ...
];
}
getProviders(): array
addExtension()
メソッドで拡張が登録されるときに呼ばれます。プロバイダの配列を返します。プロバイダはたいてい、実行時にタグが使うオブジェクトです。$this->global->...
からアクセスします。詳しくはこちら。
public function getProviders(): array
{
return [
'myFoo' => $this->foo,
'myBar' => $this->bar,
// ...
];
}
getCacheKey (Latte\Engine $engine): mixed
テンプレートがレンダリングされる前に呼ばれます。戻り値は、コンパイル済みテンプレートファイルの名前に含まれるハッシュのもとになるキーの一部になります。したがって戻り値が異なれば、Latte は異なるキャッシュファイルを生成します。