あと味

たくさん情報を食べて、たくさん発信すると、あとになって味わい深い。

Perlのデータ構造を意識しつつ、常にmt:loopタグを使う話

案件で試したわけではなく、かと言って試せる機会もないので、あくまで一つの提案です。

mt:loopタグの良い文書がGithubのmovabletype/Documentation Wikiにあります。

特筆すべき箇所が、以下のように書かれた説明です。

Movable Typeの再構築の処理を行う場合、データベースへのアクセスが多くなります。

例えばブログ記事のタイトル一覧をページ内で複数個所で利用するケースの場合、 タグを何箇所にも書くと、その個数分データベースへアクセスしに行く事になり再構築時にオーバーヘッドが多くなります。

そこで、一旦データを配列やハッシュに格納し、ループを各所でまわす事でデータベースへのアクセスを減らす事が可能となります。

データを配列やハッシュに格納すれば、あらゆるコンテナタグと呼ばれる類のタグは、mt:loopの共通のインターフェイスによって利用でき、非常にわかりやすくなると感じました。

mt:loopのインターフェイスを利用することで、もれなく、__first__, __last__, __odd__, __even__, __index__, __counter__などの特殊変数が利用できるのも大きいです。

極端に言えば、mt:entriesやmt:pages等は、「mt:loopに渡すためのデータの構築のみに使うようにする」みたいな話です。

mt:entriesを例に考えます。

mt:entriesは、通常、以下のように使うでしょう。

<mt:entries>
title: <mt:entrytitle>
text: <mt:entrytext>
more: <mt:entrymore>
</mt:entries>

これをPerlのデータ構造で表すと以下のような形で考えるのが自然ですね。

my $entries = [
  {
    title => 'title1',
    text => 'text1',
    more => 'more1',
  },
  {
    title => 'title2',
    text => 'text2',
    more => 'more2',
  },
  {
    title => 'title3',
    text => 'text3',
    more => 'more3',
  },
];

これをそのままmt:loopで使えば、かなり楽にmt:loopを活用できそうです。mt:varでアクセスできる値は、実際にはPerlのデータ構造ですからね。

結果、以下のようにテンプレートを書くことで、先ほどのデータ構造を自然に作れることを確認しました。無名ハッシュリファレンスをどうやって表現するかが悩ましいところでしたが、割とスムーズにできました。

<mt:entries>
  <mt:sethashvar name="entry">
    <mt:entrytitle setvar="title">
    <mt:entrybody setvar="text">
    <mt:entrymore setvar="more">
  </mt:sethashvar>
  <mt:var name="push(entries)" value="$entry">
</mt:entries>

以下のようにmt:loopで利用します。

<mt:loop var="entries">
  <mt:if name="__first__">
    <div class="entries">
  </mt:if>
      <article class="entry<mt:if name='__first__'> first</mt:if><mt:if name='__even__'> even</mt:if><mt:if name='__last__'> last</mt:if>">
        <h1><mt:var name="title"></h1>
        <div class="entry-text">
          <mt:var name="text">
        </div>
  <mt:if name="more">
        <div class="entry-more">
          <mt:var name="more">
        </div>
  </mt:if>
      </article>
  <mt:if name="__last__">
    </div>
  </mt:if>
</mt:loop>

これであらゆるコンテナタグを、常にmt:loopで回せそうな気がして参りました。試してませんが、元のデータ構造がもう少し複雑でも、mt:loopのネストでうまくデータにアクセスできるように思います。

個人的には、上記のmt:ifの分岐すらお腹いっぱい感があるので、もう少しテンプレートを簡潔に書けるインターフェイスを作りたいなーと思ってはいます。

まとめ

共通のインターフェイスを使えて、DBアクセスも減らせるので、メリットが大きい気がしています。やたらめったらグローバルに変数を定義しているわけでもないので、お行儀も良いです。また、基本的に<mt:if name="foo">...</mt:if><mt:var name="foo">しか使わないので、シンプルにもなります。落とし穴あるかもしれませんが...。

おまけ

データ構造を逐一調べるために、下記のような単純なMTタグを定義して、あちこちにmt:dumpを散りばめながら動作を確認しました。

tags:
  function:
    Dump: >
      sub {
        use Data::Dumper;
        Dumper shift->stash('vars');
      }

config.yamlのみでプラグインのプロトタイプをサクッと作れるのは良いですね。

追記

コメント欄で同僚さんにコメントいただいたのですが、コレが自然に動くようになったのは、MT 5.2から だそうです。どおりで、今までサンプル見なかったわけだ。あと、「落とし穴あるかもしれませんが」の回答として、ダイナミックパブリッシングで挙動が異なるケースがあるそうです。ありそうですね...。