カスタムフィルタの作成
フィルタは、Latte
のテンプレートの中で直接データを整形・加工するための強力な道具です。パイプ記号(|)を使う簡潔な構文で、変数や式の結果を望みの出力形式に変換できます。
フィルタとは何か
Latte のフィルタは、本質的には入力値を出力値に変換するために作られた PHP
関数です。テンプレートの式({...})の中でパイプ(|)記法を使って適用します。
手軽さ: フィルタを使うと、よくある整形処理(日付の書式づけ、大文字小文字の変換、切り詰めなど)やデータの加工を再利用できる単位にまとめられます。複雑な PHP コードをテンプレートで繰り返す代わりに、フィルタを適用するだけで済みます。
{* 切り詰めのための複雑な PHP の代わりに: *}
{$article->text|truncate:100}
{* 日付整形のコードの代わりに: *}
{$event->startTime|date:'Y-m-d H:i'}
{* 複数の変換を適用: *}
{$product->name|lower|capitalize}
読みやすさ: フィルタを使うと、変換のロジックがフィルタの定義に移り、テンプレートは簡潔で表示に集中したものになります。
コンテキストの認識: Latte のフィルタの大きな強みは、コンテキストを認識できることです。つまりフィルタは、自分が扱っている内容の種類(HTML、JavaScript、プレーンテキストなど)を理解し、それに応じたロジックやエスケープを適用できます。これは、とくに HTML を生成するときの安全性と正しさにとって決定的です。
アプリケーションのロジックとの統合: カスタム関数と同じく、フィルタの背後にある PHP の callable はクロージャでも、静的メソッドでも、インスタンスメソッドでもかまいません。必要ならフィルタからアプリケーションのサービスやデータにアクセスできますが、その主な目的はあくまで入力値の変換です。
Latte は既定で豊富な標準フィルタを用意しています。カスタムフィルタは、このセットをプロジェクト固有の整形・変換のニーズで拡張するものです。
複数の入力にもとづくロジックが必要な場合や、変換すべき主たる値がない場合は、カスタム関数のほうが向いているでしょう。複雑なマークアップを生成したりテンプレートの流れを制御したりしたい場合は、カスタムタグを検討してください。
フィルタの作成と登録
Latte でカスタムフィルタを定義して登録する方法はいくつかあります。
addFilter() による直接登録
フィルタを追加する最も簡単な方法は、Latte\Engine オブジェクトの addFilter()
メソッドを直接使うことです。フィルタ名(テンプレートでの使い方)と、それに対応する PHP
の callable を渡します。
$latte = new Latte\Engine;
// 引数のないシンプルなフィルタ
$latte->addFilter('initial', fn(string $s): string => mb_substr($s, 0, 1) . '.');
// 省略可能な引数を持つフィルタ
$latte->addFilter('shortify', function (string $s, int $len = 10): string {
return mb_substr($s, 0, $len);
});
// 配列を処理するフィルタ
$latte->addFilter('sum', fn(array $numbers): int|float => array_sum($numbers));
テンプレートでの使い方:
{$name|initial} {* $name が 'John' なら 'J.' を出力 *}
{$description|shortify} {* 既定の長さ 10 を使う *}
{$description|shortify:50} {* 長さ 50 を使う *}
{$prices|sum} {* $prices 配列の要素の合計を出力 *}
引数の渡し方:
パイプ(|)の左側の値は、常にフィルタ関数の最初の引数として渡されます。テンプレートでコロン(:)のあとに指定したパラメータは、それに続く引数として渡されます。
{$text|shortify:30}
// PHP の関数 shortify($text, 30) を呼びます
拡張による登録
整理のため、とくに再利用できるフィルタのセットを作ったりパッケージとして共有したりする場合は、Latte Extensionの中で登録するのがおすすめです。
namespace App\Templating;
use Latte\Extension;
class MyLatteExtension extends Extension
{
public function getFilters(): array
{
return [
'initial' => $this->initial(...),
'shortify' => $this->shortify(...),
];
}
public function initial(string $s): string
{
return mb_substr($s, 0, 1) . '.';
}
public function shortify(string $s, int $len = 10): string
{
return mb_substr($s, 0, $len);
}
}
// 登録
$latte = new Latte\Engine;
$latte->addExtension(new MyLatteExtension);
この方法ならフィルタのロジックがきれいにまとまり、登録も単純になります。
属性を使ったクラスによるフィルタ
フィルタを定義するもうひとつの洗練された方法は、テンプレートパラメータのクラスのメソッドを使うことです。メソッドに
#[Latte\Attributes\TemplateFilter] 属性を付けるだけです。
use Latte\Attributes\TemplateFilter;
class TemplateParameters
{
public function __construct(
public string $description,
// ほかのパラメータ...
) {}
#[TemplateFilter]
public function shortify(string $s, int $len = 10): string
{
return mb_substr($s, 0, $len);
}
}
// オブジェクトをテンプレートに渡します
$params = new TemplateParameters(description: '...');
$latte->render('template.latte', $params);
TemplateParameters オブジェクトがテンプレートに渡されると、Latte
はこの属性の付いたメソッドを自動的に検出して登録します。テンプレートでのフィルタ名はメソッド名と同じ(この場合は
shortify)になります。
{* パラメータクラスで定義されたフィルタを使う *}
{$description|shortify:50}
コンテキストフィルタ
フィルタが入力値だけでなく、もっと多くの情報を必要とすることがあります。処理している文字列のコンテンツタイプ(HTML、JavaScript、プレーンテキストなど)を知りたい、あるいはそれを変更したい場合です。そこで登場するのがコンテキストフィルタです。
コンテキストフィルタは通常のフィルタと同じように定義しますが、最初のパラメータの型宣言が
Latte\Runtime\FilterInfo でなければなりません。Latte
はこのシグネチャを自動的に認識し、フィルタを呼ぶときに FilterInfo
オブジェクトを渡します。それ以降のパラメータは、いつもどおりフィルタの引数を受け取ります。
use Latte\Runtime\FilterInfo;
use Latte\ContentType;
$latte->addFilter('money', function (FilterInfo $info, float $amount): string {
// 1. 入力のコンテンツタイプを確認します(任意ですが推奨)
// null(変数への適用)かプレーンテキストを許可し、HTML などへの適用は拒否します
if (!in_array($info->contentType, [null, ContentType::Text], true)) {
$actualType = $info->contentType ?? 'mixed';
throw new \RuntimeException(
"Filter |money used in incompatible content type $actualType. Expected text or null."
);
}
// 2. 変換を行います
$formatted = number_format($amount, 2, '.', ',') . ' EUR';
$htmlOutput = '<i>' . htmlspecialchars($formatted) . '</i>'; // 正しくエスケープすること!
// 3. 出力のコンテンツタイプを宣言します
$info->contentType = ContentType::Html;
// 4. 結果を返します
return $htmlOutput;
});
$info->contentType は Latte\ContentType
の文字列定数(ContentType::Html、ContentType::Text、ContentType::JavaScript
など)か、フィルタが変数に適用された場合({$var|filter})は null
です。これを読んで入力のコンテキストを調べ、書いて出力のコンテキストタイプを宣言できます。
コンテンツタイプを HTML に設定すると、フィルタが返す文字列が安全な HTML であることを Latte に伝えることになります。すると Latte はこの結果に既定の自動エスケープを行いません。フィルタが HTML のマークアップを生成する場合、これは決定的に重要です。
フィルタが HTML を生成する場合、その HTML
の中で使う入力データを正しくエスケープする責任はあなたにあります(上の
htmlspecialchars($formatted) の呼び出しのように)。怠れば XSS
脆弱性を生みかねません。フィルタがプレーンテキストしか返さないなら、$info->contentType
を設定する必要はありません。
ブロックへのフィルタ
テキスト以外のコンテンツタイプ(ふつうは HTML)を持つブロックに適用するフィルタは、コンテキストフィルタでなければなりません。ブロックの内容には定義されたコンテンツタイプがあり、フィルタはそれを知っている必要があるからです。コンテキストを扱わない従来のフィルタは、内容がプレーンテキストのブロックにしか適用できません。
{block heading|money}1000{/block}
{* 'money' フィルタは第 2 引数として '1000' を受け取り、
$info->contentType は ContentType::Html になります *}
コンテキストフィルタは、データがどの文脈で処理されるかにもとづく強力な制御を可能にし、とくに HTML を生成するときに高度な機能と正しいエスケープを実現します。