TT で custom filter (2)

前回よりだいぶ間があいてしまいました。

3. filter ファクトリクラスを作って LOAD_FILTERS で指定する

まずファクトリクラスの書き方ですが,Template::Filters というよいサンプルがあるので読みましょう。というのは冗談で,めんどくさい部分を Template::Filters に fallback させるのであれば,

package MyFilters;
use Template::Constants;

sub new { bless {}, shift }

sub store {
  my ($self, $name, $filter) = @_;
  return (undef, Template::Constants::STATUS_DECLINED);
}

sub fetch {
  my ($self, $name, $args, $context) = @_;
  
  if ($name eq 'empty_str') {
    return \&empty_str;
  }
  
  return (undef, Template::Constants::STATUS_DECLINED);
}

sub empty_str { ... }

このように書けば(Template::Base から継承する必要さえない)十分です。つまり new と store と fetch というメソッドを実装する必要があり,

  • new は説明するまでもないですよね
  • store は $tt->context->define_filter() したときに呼び出される関数;つまり外から与えられたフィルタ関数を内部に登録する関数です
  • fetch は指定された名前のフィルタ関数を(もし持っていれば)返す関数です

で,他の機構同様 FILTER まわりも,Chain of Responsibility で処理されているので,fallback として Template::Filters を指定しておくと,上記のように STATUS_DECLINED を返しとけば store とかめんどくさい部分は処理してくれます。
fetch では,フィルタ関数「そのもの」を返します。つまり,$args からその場でフィルタ関数を生成するならダイナミックフィルタ,上記のように常に同じ関数を返すのであればスタティックフィルタ,となります。間違って [ \&filter_factory, 1 ] みたいにファクトリメソッドを返さないように。

さて Template 側での設定の方法ですが,

my $ttc = Template::Context->new({
  LOAD_FILTERS => [
    MyFilters->new,
    Template::Filters->new,  # as a fallback
  ],
});

のようにファクトリインスタンスを生成・指定します。Template::Context バージョンを書きましたが,単純な Template の場合にも同じように初期化子を渡してあげればよいです(Template::Context のイニシャライザにその初期化子がそのまま渡るだけなので)。


と,長々と書いてきたのですが,ファクトリクラスを書く方法のメリットはまるでありません。

  • Template::Filters で用意されているフィルタを TT ユーザに使わせたくない
  • [% FILTER foo('bar') %] って書くのが格好悪い*1から [% FILTER bar %] と書くと自動的に bar というフィルタを生成するようにしたい

がんばって考えてみても用途はこれぐらいですかねぇ。

*1:いえ私は格好悪いとは思いませんよ。もののたとえです。