MovableTypeのダイナミックパブリッシングの仕組みを紐解く
MovableTypeを使ってサイト制作している方は数多くあれど、動的ページ生成モードの「ダイナミックパブリッシング」を深く理解している方はそんなに多くないと思います。
それは…日本語の情報が少ないから! ということで、SixApart社が提供している「ダイナミックパブリッシングの仕組み」についての公式ドキュメントを自分なりに読解してお届けしようと思います。
これが分かれば、ダイナミックパブリッシングに自前のテンプレートhtmlファイルを使ったり、独自のクラスを作ってダイナミックパブリッシングのmtview.phpに適用させたりできますよ! もちろん、エラーが起こった場合の解決の近道にもなってくれるはず。
動的ページが作られる流れ
普通に使っているぶんには知らなくても良いことですが、ダイナミックパブリッシングの振る舞いを独自にカスタマイズしたいと思っている開発者には、ページが作られていく流れを知ることは大事です。
ダイナミックパブリッシングではPHPを利用して次のようなステップでページが生成されるようになっています。
- 1. HTTPリクエスト発生
- 2. .htaccess ルール、またはエラードキュメント処理
- 3. mtview.php への委譲
- 4. MTクラス(MT.php)
- 5. MTViewerクラス、いわゆるSmarty(MTViewer.php)
- 6. PHP
- 7. HTTPレスポンスと出力
1. HTTPリクエスト発生
ユーザーからのURLリクエストが発生すると、サーバーがファイルを探し、存在したら静的URLとして扱います。 ファイルがなければダイナミックパブリッシングとして扱われます。
2. .htaccess ルール、またはエラードキュメント処理
Webサーバーで処理できなかったURLは、Apacheの「.htaccess」ファイルやIISの「error documents」に処理がわたって、アクティブなルールによる処理が行われます。
ルールには、実在するファイルやディレクトリがなければ「mtview.php」に処理が渡されるようになっています。
3. mtview.php への委譲
「mtview.php」が利用されることになると、URLリクエストがmtview.phpに渡されます。 Apache上では環境変数の「REQUEST_URI」が使われ、IISではクエリパラメーターで元のURLを利用します。
4. MTクラス(MT.php)
「mtview.php」では、URL解決処理の大部分を担うMTクラスをロードします。 URLが解決されると、Smartyオブジェクトがインスタンス化され、ページを生成します。
5. MTViewerクラス、いわゆるSmarty(MTViewer.php)
ステップ4で生成された「MTViewer (Smarty) 」オブジェクトに、ページを生成するためのデータが渡されます。 まず、アクティブなWeblog ID (blog_id)、開始・終了のタイムスタンプ、そして必要に応じて「どのアーカイブ・タイプか」というデータです。
次にURLリクエスト用のテンプレートが読み込まれ、処理されます。 SmartyによってテンプレートがまだネイティブなPHPコードになっていなければ、次の処理でネイティブコード化されます。(Smartyを知っている人なら「templates_c」ディレクトリに保存されることはご存知でしょう)
6. PHP
Smartyはコンパイル化されたテンプレート(PHPスクリプト)を読み込み、実行します。
7. HTTPレスポンスと出力
テンプレートが処理され、出力結果がユーザー側に返されます。
処理中に何らかのエラーが起こったら、ユーザーにそのことを伝えるために「Dynamic Pages Error Template」が使われます。
MTクラス
さて、MTクラスで何ができるんでしょうか? 以下はMTクラスで提供されているメソッドの中でも有名なものです。
class MT
- init_pugins() : 利用可能なプラグインを設定する
- context() : Smartyオブジェクトインスタンスを取得(実際はMovableType風味のSmartyサブクラスです)
- db() : データベースインスタンスを取得
- configure() : MovableTypeの設定ファイルをロードする
- configure_paths() : MovableTypeのパスの位置をセットアップする
- view() : ダイナミックパブリッシングのメイン処理
- resolve_url() : 引数のURL用のデータベース記録を返す
- display(): 特定のSmartyテンプレートを表示するためのユーティリティ関数
- fetch() : 特定のSmartyテンプレートによる出力を取得するためのユーティリティ関数
- error_handler() : カスタムエラー処理関数
- doConditionalGet() : 条件付きのGET処理を扱う
MTクラスの何がいいかというと、単純にそれが「クラスになっている」ということですね。 どういうことかというと、あなたがそれをカスタマイズできるということなんです。
だから、処理の流れが気に入らなかったら、好きなように変更できるんですよ。
あんまり深くまで詮索したくなければ、リクエストを受け取ってから出力を返すまでの間に「mtview.php」内で何をさせるかをカスタマイズすればいいだけ。
MTDatabaseクラス
PHPを利用したMovableTypeにおけるデータベース・アクセスのレイヤーはPerlのMovableTypeアーキテクチャとの大きな別れです。 比較すると、とてもシンプルにできています。
MovableTypeでは、異なるDBベンダー(MySQL, PostgreSQL, SQLite)間でのレイヤーの抽象化のためにezSQLパッケージを基本クラスに利用しています。
でも、データベーステーブルごとに異なるオブジェクトを持つ代わりに、シンプルにPHPの配列(あるいはハッシュ)を取得するようにしています。
だから、テーブルレベルでエントリーデータやその他のデータを取得するのに「load」メソッドがありません。 その代わり、データベースクラス内のメソッドにその種のデータを取ってくるものがあります。
これで、ダイナミックパブリッシングにおいて貴重なCPU時間を節約することができます。
抽象化されたクラスとしてMTDatabaseBaseクラスというものがあります。 実際にインスタンス化される場合は、DBベンダーに特化したサブクラスが使われます。
特化したサブクラスとしては、「MTDatabase_mysql」「MTDatabase_postgres」「MTDatabase_sqlite」があります。(MySQLクラスだけが現時点(2004/9月時点)で実用的なクラスです)
DBベンダーに特化したクラスは、基本クラスの関数を適切なシンタックスのSQLクエリーになるようにオーバーライド、あるいはインプリメント(implement)しています。
データベースクラスで利用できるメソッドの一部です。
class MTDatabaseBase ( ezSQLクラスを継承 )
- resolve_url() : 引数のURLを表示するのに必要なコンテキストデータを返す
- load_index_template() : 特定のインデックステンプレート用のmt_templateデータを返す
- load_special_template() : 特定のテンプレートクラス用のmt_templateデータを返す
- get_template_text() : 特定の名前のテンプレート用のテンプレート・ソースを返す
- get_archive_list() : <MTArchiveList>タグのための関数
- archive_link() : 引数のタイムスタンプやアーカイブタイプに一致するURLを返す
- fetch_blog() : 引数のブログIDに一致する個別のブログデータを返す
- blog_entry_count() : 引数のブログIDのグログのエントリー数を返す
- fetch_entry() : 引数のエントリーIDに一致する個別エントリーデータを返す
- fetch_entries() : 要求された条件にマッチするエントリーデータ配列を返す
- entry_link() : 引数のエントリーIDやアーカイブタイプに一致するURLを返す
- entry_comment_count() : 引数のエントリーIDに一致するエントリーのコメント数を返す
- entry_ping_count() : 引数のエントリーIDに一致するエントリーのPing数を返す
- fetch_category() : 引数のカテゴリーIDに一致するカテゴリーのデータを返す
- fetch_categories() : 要求された条件にマッチするカテゴリーデータ配列を返す
- category_link() : 引数のカテゴリーIDに一致するカテゴリーのURLを返す
- fetch_author() : 引数の執筆者IDに一致する執筆者のデータを返す
- fetch_comments() : 要求された条件にマッチするコメントデータ配列を返す
- fetch_ping() : 要求された条件にマッチするPingデータ配列を返す
上に挙げたメソッドはキャッシュされていればできるだけキャッシュを利用するようになっています。
MTViewerクラス
最後にMTViewerクラスです。 このクラスはSmartyクラスの子孫クラスです。
class MTViewer (Smartyクラスを継承)
- add_global_filter() : Perlを利用したMovableTypeの時と同じようにグローバル・フィルターを登録します。
- error() : 実行時にエラーを発生させます
- this_tag() : プラグイン・ルーチンの中で、現在アクティブになっているMTタグ名を返します
- stash() : MTのstashメソッドと似ています。 stash上のデータを設定・取得します。
- localize() : stash内にある要素のリストを保存します
- restore() : 以前「localized()」したstash要素のリスト状態を元に戻します
- tag() : MTタグ用の命令を呼び出し、結果を返します
上に挙げたメソッドに加えて、MTViewerクラスにはテンプレートが処理される方法もカスタマイズできます。 「mt_to_smarty」と呼ばれるカスタムSmarty prefilter が定義されていて、MovableTypeのテンプレートをSmarty互換の文法に変換する役割を持ちます。
mt_to_smarty によって「<MTEntries>…</MTEntries> -> {Entries}…{/Entries}」というように変換されます。
mtview.php をカスタマイズしてみよう
すでにお話したように、メインのMTクラスはオブジェクトなので、必要であればカスタマイズすることができます。 例えば、URLリクエストを元にエントリーを検索するための代替機能をresolve_url()メソッドをオーバーライドすることで実装したいとしましょう。
その場合は「mtview.php」に次のようなスクリプトを書くことで実現できます。
1 <?php 2 include("MT_DIR/php/mt.php"); 3 class MyMT extends MT { 4 function &resolve_url($path) { 5 $data =& parent::resolve_url($path); 6 if (!$data) { 7 # データが見つからなかったら独自関数を試してみましょう 8 $data =& $this->fuzzy_resolve_url($path); 9 } 10 return $data; 11 } 12 function &fuzzy_resolve_url($path) { 13 # 引数の$pathを利用した独自の処理ができます 14 } 15 } 16 17 $mt = new MyMT(1, "MT_DIR/mt.cfg"); # 自作のMyMTクラスを使います 18 $mt->view(); 19 ?>
【注意】
上記の公式コードは若干古いようで、今のバージョンではコンストラクタがプライベートメソッドになっているため、継承先で使えません(インスタンス化する際にエラーになります)。
そのため、MTクラスが持っている$_instance変数、__construct()メソッド、get_instance()メソッドをコピーしてクラス名を「MyMT」に変えます。 つまり、オーバーライドします。
class MyMT extends MT { private static $_instance = null; private function __construct($blog_id = null, $cfg_file = null) { error_reporting(E_ALL ^ E_NOTICE ^ E_WARNING); try { $this->id = md5(uniqid('MT',true)); $this->init($blog_id, $cfg_file); } catch (Exception $e ) { throw new MTInitException( $e, $this->debugging ); } } public static function get_instance($blog_id = null, $cfg_file = null) { if (is_null(MyMT::$_instance)) { MyMT::$_instance = new MyMT($blog_id, $cfg_file); } return MyMT::$_instance; } }
そして以下のようにget_instance()を呼び出して$mtを取得します。 あとは、好みに応じてview()メソッドなどをオーバーライドしてカスタマイズします。
16 //引数の1つ目はブログID、2つ目は設定ファイルへのパスです。 17 $mt = MyMT::get_instance(1, "MT_DIR/mt-config.cgi");
次に、このPHPスニペットを見てみてください。
1 <?php 2 include_once("MT_DIR/php/mt.php"); 3 $mt = new MT(10); # id:10のブログは「linkblog」です 4 $ctx =& $mt->context(); 5 $ctx->caching = 2; # キャッシュの時間はファイル毎に設定します 6 $ctx->cache_lifetime = 60 * 30; # キャッシュは30分です 7 $output = $mt->fetch("mt:My Linkblog"); 8 echo $output; 9 ?>
このコードは、ダイナミックパブリッシング・エンジンを使って「My Linkblog」という名前のインデックステンプレートを出力しているところです。 (「mt:」という修飾子は、テンプレートをデータベースから取ってくることを表します。 Smartyでは「file:」という修飾子もあって、これはファイルシステムからテンプレートを取ってくることを表します) 出力は1,800秒(30分)キャッシュされます。 このような処理はあらゆるPHPスクリプト上で可能です。
サイト全体を静的に取得しつつ、PHPエンジンを使ってこのように部分的なデータを取得することもできます。
ダイナミック・テンプレートを使うと、データへ手軽にアクセスできます。 処理中にフル・スクリプティング言語が使えるので、MovableTypeプラグインを作るような負担を軽減します。 すでにあるPHPの豊富な関数群や、PHPライブラリ、PHPモジュールも使えます。 次の例は、MovableTypeのコンテンツへPHPを使って直接アクセスする方法や、操作方法、好みの作法で出力できることを表しています。
1 <MTEntries lastn="10"> 2 <?php 3 $title = $this->tag('MTEntryTitle'); 4 $raw_body = $this->tag('MTEntryBody', array('convert_breaks' => '0')); 5 $raw_body = preg_replace('/[^A-Za-z0-9s]/', '', strip_tags($raw_body)); 6 $raw_words = preg_split('/s+/', $raw_body); 7 echo $title . " (approx. word count: " . count($raw_words).")"; 8 ?> 9 </MTEntries>
テンプレートタグをPHP内で使ってみる
(現時点で)1つ欠点があるとすれば、PHPのコードブロック内でMTテンプレートタグが使えないことです。 スタティック・パブリッシングモデルではこれができるのですが、ダイナミックパブリッシング下では、MTタグはPHPコードに変換されてしまいます。 だから、PHPコードブロック内に書いたMTタグはPHPコード内でPHPのオープンタグを出力してしまいます。 つまり、シンタックスエラーになります。 よって、おすすめの方法としては、上の例で挙げてあるようにtag() メソッドを使って、タグを呼び出すようにしてください。
さらに、Smartyフレームワークを使うと、ページの出力をさらにカスタマイズできます。 Smartyを利用することでできるようになるのが、アウトプット・フィルターです。 アウトプット・フィルターは、テンプレートが処理された後に、何らかの処理を適用することができます。 たとえば、ページ全体(個別のエントリーテキストではなく)に特定のテキストフィルターをかけたい時、次のようなアウトプット・フィルターをロードすることができます。
PHPプラグインのディレクトリに以下のコードで「outputfilter.smartypants.php」というファイルを作って入れておきます。(PHPバージョンのSmartyPantsはここにあります。
1 <?php 2 include_once("smartypants.php"); 3 function smarty_outputfilter_smartypants($text, &$ctx) { 4 return SmartyPants($text); 5 } 6 ?>
そして、mtview.php 内でシンプルにフィルターをロードします。
1 # $mt->view() の直前に以下のコードを記述 2 $ctx =& $mt->context(); 3 $ctx->load_filter('output', 'smartypants');
ここではページ全体のフィルタについて取り上げましたが、MTコンテンツにも応用できます。 幸いにも、SmartyPantsパーサーはきれいにHTMLやスクリプト等を無視します。
Smartyテンプレートも動きます
MovableTypeのテンプレートを処理するエンジンの根底にSmartyが使われているということは、必要であれば全てのSmartyテンプレートのシンタックスが利用できるということです。 さらにMTタグとSmartyのテンプレート・コードを混在させることもできます。(Smartyの標準デリミタはJavaScriptのコードでエラーが起こらないように、「{…}」から「{{…}}」に変更されています)。 また、PHPスクリプトも呼び出すことも可能です。
たとえば、Smartyには「cycle」という素敵なファンクション・タグがあります。 Cycleを使うと、リストの値を循環させることができます。 cycleがループ中に利用されるたびに、リストの値を出力します。 使い方はこんな感じ。
1 <MTEntries> 2 <tr bgcolor="{{cycle values="#eeeeee,#d0d0d0"}}"> 3 <td><$MTEntryTitle$></td> 4 </tr> 5 </MTEntries>
これによって、テーブルの行ごとに異なる背景色が設定されるようになります。
Smartyのマニュアルについてはこちらを読んでみてくださいね。
2011-03-08