MTがなぜ大規模なテンプレートエンジンとアーカイブエンジンを持つに至ったかというと、全てはMTEntriesを動かすためといっても過言ではありません。
MTEntriesを制するものはテンプレートを制す、と言っても良いでしょう。

というわけで、MTEntriesのMT4.01での実装を、細かく調べてみました。

以下、MTEntriesの内部動作について気をつけたい点を一通り確認した後、実際のコードの流れを追う形で具体的な処理の詳細を見てみます。

概観



コンテキスト



MTEnriesの行う重要な動作の一つに、モディファイアやコンテキストの指定をもとに、表示するべきエントリーを絞り込むことがあります。
コンテキストから読み取る情報については、以下の2種類に分類することが出来ます。


具体的なエントリー一覧として設定されているもの(キャッシュ)

アーカイブに含まれるエントリーや、MTEntriesの外側のブロックタグで絞り込み済みのエントリーなど、直近の物が$ctx->{stash}{entries}に格納されています。
ロード済みの(メモリに格納された)エントリーの実体がある場合と、MT::Promiseによって、ロード内容をコードとして持っているが実行していない場合があります。

日付の範囲やカテゴリーなど、絞込みの条件指定

たとえば月別アーカイブでは、月の始まり 20071101000000 と月の終わり 20071130235959 が設定されています。



class



MT4から導入された「ウェブページ」機能で作成されたページは、内部的にはエントリーとして保存されています。そのため、MTEntriesで扱おうとしているエントリーが、本当にエントリーなのか、それともウェブページとして作成されたものなのかを判別するためにclassという概念が導入されています。MTEntriesにはclassを判断するためのルーチンが含まれています。

フィルタ



各種絞込み条件は、「フィルタ」という形で適用されます。
フィルタは各種の条件に合わせて作成され、フィルタ一覧に格納されます。
そして、事前に用意された、表示候補となるエントリー一覧をフィルタリングします。候補中の各エントリーそれぞれに対してテストを行い、全てのフィルタを通過したエントリーのみを、実際に表示するエントリーとして登録する仕組みになっています。


動作の流れと詳細



大まかな動作の流れとしては、以下のようになります。


  • コンテキストとモディファイアを調整

  • フィルタを作成

  • エントリーの読み込み

  • フィルタリング

  • ソート

  • ブロック内部をビルド



ではいきます。

コンテキストとモディファイアを調整



いわゆる事前準備ですね。枝葉末節までだーっと箇条書きにしておきます。


  • 最初に、sort_byモディファイアにscoreが指定されているにも関わらず、namespaceモディファイアが無い場合、エラーを発生させて終了する、という処理があります。

  • 続いて、複数ブログの指定を展開します。blog_id、blog_ids、include_blogs、exclude_blogsなどのモディファイアが絞込み条件に適用されます。問題があった場合はエラーを発生させて終了します。

  • 以下の条件を満たしている場合、キャッシュを破棄します。

    • class_typeモディファイアでのclassの指定と、キャッシュされているエントリーのclassが異なっている場合

    • 次のモディファイアのうちのいずれかが指定されている場合('category', 'categories', 'tag', 'tags', 'author', 'id', 'days', 'recently_commented_on', 'include_subcategories')


  • ここで内部動作的な処理として、テンプレート上でのoffset指定がautoで、かつ再構築実行時のCGIのクエリにoffsetという指定があったら、cgiのクエリを反映させるという処理が入ります。

  • モディファイア「limit」の値がautoなら、ブログの設定にある「ブログ記事の表示数」を絞込み条件に反映させます。また、limitにauto以外の指定があった場合は、モディファイアlastnをlimitの値で上書きします。

  • この時点でキャッシュが存在しない、かつモディファイア「category」が設定されていない場合、コンテキストにカテゴリーの設定があると絞込条件に反映されます。(モディファイア「categories」は見ない)




フィルターの作成



カテゴリ


カテゴリのフィルタを作成します。以下の優先順位で指定が存在するかを検査していき、最初に見つかった指定に基づいてカテゴリーのフィルタが作成されます。


  1. モディファイアcategory の指定

  2. コンテキストの mt_categories の指定

  3. コンテキストの archive_category の指定

  4. モディファイア categories の指定



モディファイア「category」「categories」には「AND」「OR」「NOT」を使った複数の条件が指定できます。


タグ


続いてタグによるフィルタの作成です。
モディファイア「tag」または「tags」の指定があれば、タグフィルタを作成します。重複して指定がある場合は「tag」の指定のみが有効になります。
モディファイア「tag」または「tags」には、「AND」「OR」「NOT」を使った複数の条件が指定できます。

投稿者


モディファイア「author」によるフィルタを作成します。これはまあ、そのままです。

id


モディファイア「id」によるフィルタを作成します。・・・あれ、こんなの何時の間に出来たんでしょうか。

普通にテンプレートからモディファイアを指定する場合は、現在のところ、単独のidを数字で指定することしか出来ません。
しかし、おそらくは内部利用を目的として、配列へのリファレンスも受け取ることが出来るようになっています。ここに、MTSetVarで作成した配列を渡すことで、複数のidを指定した絞込みが可能になっています。


<mt:setvar name="my_id_array[0]" value="343">
<mt:setvar name="my_id_array[1]" value="314">
<mt:entries id="$my_id_array">
<mt:entrytitle>
</mt:entries>


idによる指定で直接エントリーをリストアップする、というのは、MT3では手が届かない痒い場所だったので、嬉しい機能ですね。

unique


続いてモディファイア「unique」によるフィルタを作成します。
おそらくは、unique="1"のように指定すると、エントリーが重複しないように監視してくれるフィルタが作成されるのだと思うのですが、使用例を思いつきませんでした><

そしてboomerへ


今回は詳細については触れませんが、MTOS/MT4.1の最新リビジョンではここでさらに、以下のモディファイアを組み合わせてフィルタを作成出来るようにコードが追加されています。怒涛ですね。


  • scored_by

  • min_score

  • max_score

  • min_rate

  • max_rate

  • min_count

  • max_count



以上でフィルタ作成編は終了です。

エントリーの読み込み



フィルタリングを行うもととなる、エントリーの母集団を作成します。
この時点でキャッシュが有効ならキャッシュが利用され、このステップは省略されます。キャッシュが無い場合には、ここまでの処理で作成された日付情報を元に、データベースからエントリーがロードされます。

日付情報を、フィルタとして適用するのではなく、ロード時に直接適用するのは、おそらく高速化を目的とした処置かと思われます。
また、この辺りからソートに関する処理も入ってくるのですが、これも高速化のために可能な限りデータベースの機能を利用しようと、複雑なフラグ管理が行われています。

日付情報の優先順位ですが、モディファイア「days」が指定されている場合、これが最優先で使われます。日付アーカイブ内であらかじめコンテキストに設定されている日付範囲よりも優先されると思います。

次に、以下のモディファイアのいずれも指定されていない場合、ブログの設定にある「ブログ記事の表示数」の設定が参照されます。この条件に当てはまる場合、これもコンテキストに設定されている日付範囲よりも優先されるような気がします。
スイッチとなるモディファイアは以下の通りです。

'lastn', 'category', 'categories', 'tag', 'tags', 'author', 'days', 'recently_commented_on'

これらのうちのいくつかは、MTEntries内部で自動的にセットされる場合もあるので、必ずしもテンプレートの記述とは一致しません。

最後に、この一覧のいずれかのモディファイアが設定されている場合には、コンテキストに設定されている日付範囲が適用されます。

こうして設定された日付範囲と、classの指定に基づいて、データベースからエントリーのロードを行い、フィルタリングに備えます。

フィルタリング



作成されたフィルタが適用されます。全てのフィルタを通過しない限り、エントリーが実際に出力されることはありません。また、同時に、limit/offsetによるエントリー数の足切りも行われます。(フィルタが一つも存在しない場合、データベースからのロード時に足切りされています。)
また、フィルタリング後に、モディファイア「recently_commented_on」が有効な場合には専用のソートが行われます。

ソート



基本的にソートはデータベースからのロード時に行うようになっているのですが、モディファイア「sort_by」や「sort_order」の設定内容によっては、この時点でソートを行う必要があるようです。

ブロック内部をビルド



ここから先は、一般的なMTのブロックタグの定型処理となるので、特筆すべきことはあまり無いです。
最終的に選ばれたエントリーについて、それぞれコンテキストを設定した上でブロック内部のテンプレートをビルド、結果を連結して返却、終了、という流れです。

アンドキュメンテッドな動作としては、モディファイア「glue」による区切りの挿入が可能なことと、最終的に出力するエントリー数が0だった場合にMTElseを呼べるようになっていること、くらいでしょうか。

終わり



最後まで読まれた方、お疲れ様でした。

これまでも細かい点の確認のためにチラッとコードを見ることはよくあったのですが、一つのルーチンを上から下につぶさに読んで全部メモをとる、というのは初めての経験で、途中で心が骨折しそうになりました。ていうか折れました。
実際後半はだいぶはしょってしまいました。ソートについてはほとんどスルー状態ですし。日付範囲の条件の優先順位あたりも、あまりに複雑でちょっと自信ないです。

まあ、基本的には自分用ドキュメントなので、後で勝手に直したり追記したりします。

ではおやすみなさい。