プログラミングの最近のブログ記事

Data::ObjectDriver::BaseObject::begin_work、rollback、commitを使う。

MTのコードにはこのあたりを考慮していないものが多すぎる。権限(Association)を割り当ててからユーザーのパーミッション(Permission)を更新する処理など、あちこちに投げっぱなしのコードがある。数万件のレコードを管理画面操作で更新する等。

複数のレコードを纏めてINSERT、UPDATEするなどの処理をループで回さなければならなかったり、速度面の問題であったり、あの忌まわしいMT::Meta::Proxy::save 問題であったり、そういうところにもっと目を向けて真摯に取り組まないと未来はないよ。


    my @entries = MT->model( 'entry' )->load( undef, { sort => 'id', direction => 'descend' } );
    my $driver = MT::Object->driver;
    $driver->begin_work;
    eval {
        for my $obj ( @entries ) {
            print $obj->id . "\n";
            # if ( $obj->id < 800 ) {
            #     die; # ここでdieした時、すべてのレコードは更新されない
            # }
            $obj->excerpt( 'Hello World.' ); # 概要欄に値をセット
            $obj->save or die $obj->errstr;
        }
    };
    if( $@ ) {
        $driver->rollback;
        die $@;
    }
    $driver->commit;

この記事は Movable Type Advent Calendar 2016 の最終日の記事です。

再構築ウィンドウ

Movable Type Advent Calendar は2012年から参加していて毎年最終日を担当させていただいています。過去の記事はこちら。

2016年のネタ。2017年、MTはもっと高速になる!

今年は何を取り上げようかと思っていましたが、丁寧に記事を書くモチベーションと時間が足りず、とはいえ負けるのも(誰に?)悔しいので、以前から気になっているMTの箇所にパッチを当てて再構築のスピードを速める実験をしてみました。

静的ファイルジェネレーターとしての Movable Type

少しだけもったいつけて前振りを。静的サイトジェネレーター Advent Calendarで Movable Typeの中の人である長内さんが記事を書かれていますね。そう、MTは静的ファイルを吐き出すことのできるCMSです(ダイナミックパブリッシングも可能、部分的ファイナミックパブリッシングも可能、もちろんダイナミック処理ではキャッシュの仕組みを備えています)。

今年は第2回CMSプロレス 多言語サイトタイトルマッチ - CMS SUNDAYにお呼ばれして、チームMT(一人だけど)として参加、見事に勝利してきました。Movable Type以外の参加CMSはDrupal、concrete5、TYPO3、WordPress。Movable Typeだけが異質だと思いませんか?

  • MT以外のCMSはすべてダイナミック前提
  • MT以外のCMSはすべてPHP(MTは基本Perl、ダイナミックはPHP)

PHP、Perlの違いはまぁ置いておいて、ダイナミックとスタティック(静的サイト)だと、文化が違うというか話がかみ合わないんです。同じく中の人の澤さんに「勘所がわからん!」とか言ってますね。

Facebookにて、ピンチを迎える私!

そう、動的、ダイナミックなCMSが「高速ですよ」っていったらみんなキャッシュの話静的ファイルジェネレーターでキャッシュって、あるとしたらCDNやwebサーバーチューニングとかの話です。もちろんダイナミックも持ってるからキャッシュの話ついていけるけど、URLリライト系の話も盛り上がるみたいなんですよねあっちの文化では。てことで勘所あんまりわかりませんでした前半は。

静的ファイルジェネレーターで「高速」ってのは一般にはいわゆるパブリッシュ(再構築)の話ですよね。Movable Typeの再構築はしばしば待ち時間が嫌だとかそういう話になります。ただ、エンタープライズCMSの世界では、そこあんまり話題にならないんですよね。CMS操作の重さより、公開サイトの遅さや負荷とかそっちを気にされます。ま、そういうのも含め(使われているサイトの規模や用途も含め)て文化の違いとしか表現しようがありません。ま、なんとか勝ててよかったです。

MTの再構築はまだ速くなる余地がある

今年、ある案件で公開側のサイトのダイナミック処理の速度が思ったように出ずに調査していた時に、テンプレートのコンパイルキャッシュが非効率な処理になっているケースを発見しました。以下の記事にその顛末をまとめています。

その時に「あれ? 静的ファイルの再構築処理の際の MTタグ(MTML)のコンパイルってどうなってんだっけ? って見たんです。どうもキャッシュされている気配がない。

で、少し手を入れてみたんですがうまく行かず、途中のファイルをFogBugzに投げてそこで止まってました。

MT::Builder の compile に手を入れる

MT::Builder の compile てのは、乱暴にひと言で言えば、テンプレートのMTタグをパースしてコンパイルして{tokens}を組み立てているところです。そのあと、テンプレートはコンテキストに沿ってビルドされます。MTはSQLの実行結果やビルドしたブロックやモジュールなどはマメにキャッシュしていますが、このコンパイル結果をキャッシュしていません。以下、見てもらえますか!? そう、MTタグを正規表現でパースしているんですよ、ここ。

    while ( $text
        =~ m!(<\$?(MT:?)((?:<[^>]+?>|"(?:<[^>]+?>|.)*?"|'(?:<[^>]+?>|.)*?'|.)+?)([-]?)[\$/]?>)!gis
        )
    {

ご存知の通り(知らない人も多いでしょうけど)、Movable Typeでは、再構築処理を行う際に1つのCGIへのリクエストあたり40記事ファイルずつ再構築を行い、リダイレクトを繰り返してすべてのファイルを再構築します。この40という数字は環境変数で変えることができます。

つまり、1リクエストでの再構築処理で同じテンプレートが少なくとも40回パースされるわけです。こいつを一回で済ませてしまえば。

mt/lib/MT/Builder.pm

12a13
> use Data::Dumper;
32a34
>     my $is_tmpl;
34a37,39
>         if ( $ctx->id ) {
>             $is_tmpl = $ctx->id;
>         }
73a79,88
>     
>     my $cache_key;
>     my $chached_state;
>     if ( $is_tmpl && $text ) {
>         $cache_key = 'compiled-cache-' . MT::Util::perl_sha1_digest_hex( $text );
>         if ( $opt ) {
>             $cache_key .= '.' . MT::Util::perl_sha1_digest_hex( Dumper $opt );
>         }
>         $chached_state = MT->request( $cache_key );
>     }
82a98,101
>     
>     if (! $chached_state )
>     {
>     
291a311,316
> 
>     } else {
>         $state = $chached_state->{ state };
>         $depth = $chached_state->{ depth };
>         $errors = $chached_state->{ errors };
>     }
306c331,338
< 
---
>     if ( $cache_key ) {
>         if (! $chached_state ) {
>             $chached_state = { depth => $depth,
>                                state => $state,
>                                errors => $errors };
>             MT->request( $cache_key, $chached_state );
>         }
>     }

ソースファイル(Builder.pm)

マシンスペックの高い環境では、EntriesPerRebuildの値を大きくするほどその効果が顕著になり処理も速くなります。

計測結果

このブログをエクスポートして、最新版のMT6.32にテーマ Rainier 1.22 を入れた環境を用意して計測しました。「デフォルト、すべてのファイルの再構築計測結果(ケース1)」と、「記事アーカイブのみ、EntriesPerRebuildに1000を指定した場合(ケース2)」各5回の計測結果はこちら。

計測ケースケース1ケース2
Builder.pmオリジナルBuilder.pm改良Builder.pmオリジナルBuilder.pm改良Builder.pm
1回目331243216155
2回目302254270170
3回目285234211180
4回目301255189155
5回目281245170155
合計(秒)150012311056815
結果約18%高速約22%高速

2017年、もっとMTが速くなるための宿題

あまり時間もなかったので、今日のところはこれまでです。引き続き以下のような部分を検討して実装できれば2017年、MTの再構築はもっと速くなることでしょう!

  • まずは、このコードの影響度など、問題ないかの確認・検証
  • 現状template_id 指定がある場合のみキャッシュしているが、これ以外にキャッシュできるケースがないかの検討
  • リクエストをまたがって memcachedやMTのキャッシュ(MT::Session)などへのキャッシュ

それでは、メリークリスマス、そして、良いお年をお迎えください!

記念に貼っておく。ただそれだけ。中盤から後半は結構無理やりだけどね。この歳になって初めて bashスクリプトを書いた(sedとか書いたのも初めてだ!)。

#!/bin/bash
lftp -u usrname,password -p 21 example.ftp.azurewebsites.windows.net << EOF
cd /site/archives
mirror
bye
EOF
find . -f>../files.txt
sed -i -e 's/^[^\/]*$//g' ../files.txt
sed -i -e '/^$/d' ../files.txt 
sed -i -e 's/^/rm /g' ../files.txt
commands=`cat ../files.txt`
lftp -u usrname,password -p 21 example.ftp.azurewebsites.windows.net << EOF
cd /site/archives
${commands}
bye
EOF
rm ../files.txt

参考

1年半ぶりくらいなので、結構忘れている。メモ大切。

Apple Developer サイト

  1. キーチェーンアクセス.appを起動 > キーチェーンアクセスメニュー > 証明書アシスタント > 認証局に証明書を要求 (証明書は「ディスクに保存」)
  2. Apple の Developerサイトから「Account」へ進む
  3. 「Certificates, Identifiers & Profiles」(真ん中のアイコン) へ進む
  4. 「Mac App Distribution」 を選択
  5. 1.で作成したファイルをアップロードすると証明書が生成されるので、ダウンロードして保存
  6. 続いてMac Installer Distribution を選択し、5.を繰り返す
  7. ダウンロードした証明書を開いてキーチェーンアクセスに登録する
  8. キーチェーンアクセスの画面で名前を確認しておく(例「3rd Party Mac Developer Installer: Alfasado Inc. (XXXXXXXXXX)」「3rd Party Mac Developer Installer: Alfasado Inc. (XXXXXXXXXX)」) 前者はアプリに、後者はインストールパッケージに付与するもの

証明書の種類を選択

productname.entitlementsファイル(名前はなんでも良い)を用意する。最低限「com.apple.security.app-sandbox」に「true」の指定が必要。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
</dict>
</plist>

ターミナルでcodesignコマンドでアプリに署名を付け、productbuildコマンドでインストールパッケージに署名を付ける。

sudo codesign -f -v -s "3rd Party Mac Developer Application: Alfasado Inc. (XXXXXXXXXX)" --entitlement /path/to/productname.entitlements "/Applications/ProductName.app"
sudo productbuild --component /Applications/ProductName.app --sign "3rd Party Mac Developer Installer: Alfasado Inc. (XXXXXXXXXX)" --product "/Applications/ProductName.app/Contents/info.plist" ProductName.pkg

最後にApplication LoaderでAppleへの登録を行う。

9月3日(土) メビック扇町で開催された CMS大阪夏祭り2016の懇親会で ColorTester について Lightning Talk してきました。

CMS大阪夏祭り2016懇親会で Lightning Talk する筆者の写真

[Photo by NExT-Season]

スライドに補足入れてますが、JIS X8341-3(WCAG2.0)において、ロゴタイプにはコントラストの要件はありません。

1.4.3 最低限のコントラスト: テキスト及び画像化された文字の視覚的な表現には、少なくとも 4.5:1 のコントラスト比をもたせる。ただし、次の場合は除く: (レベルAA) … ロゴタイプ: ロゴ又はブランド名の一部である文字には、コントラストの要件はない。

なので、このLTについては、各CMSのロゴのコントラスを論じるのが目的ではなく、画像の中の主たる文字の色についてどのように推察しているのかのロジックを紹介するものです。過去にもこのブログで取り上げたことがあります。

先日、Movable Type6.3がリリースされました。

一番の変更点はダイナミックパブリッシングのPHP7サポートですが、内部的には Smartyのバージョンが2系から3系に変更になっています(PowerCMS/DynamicMTMLの対応は少々お待ち下さい)。

PHP 7 をサポート

PHP 7 を正式にサポートしました。PHP 7 は、PHP 5系と比べてもパフォーマンスの向上が期待できます。

Smarty 3.1 へアップデート

PHP 7 への対応に伴い、Smarty 2 から Smarty 3 へのアップデートされました。

プラグイン開発者へ

ダイナミック・パブリッシングに対応したプラグインについて、直接 Smarty を操作していないかぎりにおいて変更の必要は無いものと想定していますが、動作確認等よろしくお願いいたします。

※(再度) PowerCMS/DynamicMTMLの対応は少々お待ち下さい

MTViewerとは、$ctx であり Smartyである

何のことか分かる人にしかわからないアレゲな書き方ですが、つまるところMTにおけるダイナミック・パブリッシングとはMTのテンプレートをSmartyテンプレートにコンバートしてビルドするという仕組みになっています。実体は mt/php/lib/MTViewer.php です。

MTViewer.phpのコード

この MTViewerクラスの fetchメソッドがテンプレートをビルドするコードなのですが、MTタグをPHPの中で動的にビルドする方法について備忘録がてらメモしておきます。ちなみに、元がSmartyですので、これらはSmartyのテンプレートを渡しても動作します。MTテンプレートを指定した場合、mt/php/lib/prefilter.mt_to_smarty.php がMTのテンプレートをSmartyのテンプレートに変換してからビルドされます。というわけでMTViewerクラスの fetchメソッドにはMTタグをそのまま渡せるのです。

MTテンプレートをPHPコードの中でビルドする

念のためですが、$ctx の初期化については下記の記事を参照ください。

template_id 指定でビルド

$mt = MT::get_instance();
$ctx =& $mt->context();
$str = $ctx->fetch("mt:123");

テンプレート名(template_name) 指定でビルド

$mt = MT::get_instance();
$ctx =& $mt->context();
$str = $ctx->fetch("mt:テンプレート名");

テンプレート指定のビルドについては mt/php/lib/resource.mt.php で定義されています(Smartyのカスタムリソース)。 Smartyのカスタムリソースの作り方は以下のページに。カスタムリソースを作成すれば、例えば S3 に置いたMTテンプレートを指定してビルド、とかまぁ色々できるわけですね。

MTテンプレート文字列指定でビルド

$mt = MT::get_instance();
$ctx =& $mt->context();
$str = $ctx->fetch("eval:<mt:Date>");

ファイルパス指定でビルド

$mt = MT::get_instance();
$ctx =& $mt->context();
$str = $ctx->fetch("file:/path/to/tmpl.tpl");

テンプレート変数をセットしてビルド

$mt = MT::get_instance();
$ctx =& $mt->context();
$ctx->__stash[ 'vars' ][ 'foo' ] = 'bar';
$str = $ctx->fetch('eval:<mt:Var name="foo">');

記事コンテキストをセットしてビルド

$mt = MT::get_instance();
$ctx =& $mt->context();
$entry = $mt->db()->fetch_entry( 1 );
$ctx->stash( 'entry', $entry );
$str = $ctx->fetch('eval:<mt:EntryTitle>');

この辺りを理解しておくと、PHPでサイトを拡張していく際にMTテンプレートが活用できるようになります。例えばPHPで作成したフォームメールのテンプレートにMTタグを使ったり。ということでMT6.3の Tips でした。

こんなページができていて、いい傾向じゃないの(上から目線w)、と思ってまして。

ツッコミと言うわけではないですが、僕も最近色々思ってたことが触れられてたので。

ま、同じことなんですけど、何でこういうエントリを書いてるかというと、PerlでMTMLやHTMLを突っ込むのについついPerlのヒアドキュメントを使っちゃうことがあるからなのです。ひとつのファイルで完結するからどうしても手を抜いてそういう実装をしがちなのですが。

僕なら、テンプレートを tmpl/include に置いた上でこう書きます。

sub template_param_edit_entry {
    my ( $cb, $app, $param, $tmpl ) = @_;
    $app->{ plugin_template_path } =
        File::Spec->catfile( MT->component( 'AwesomePlugin' )->path, 'tmpl' );

    my ( $node, $attr, $include );

    $node = $tmpl->getElementById( 'header_include' );
    $attr = { name => 'include/edit_entry_header.tmpl' };
    $include = $tmpl->createElement( 'include', $attr );
    $tmpl->insertBefore( $include, $node );

    $node = $tmpl->getElementById( 'title' );
    $attr = { name => 'include/edit_entry_title.tmpl' };
    $include = $tmpl->createElement( 'include', $attr );
    $tmpl->insertBefore( $include, $node );
}

DOMに慣れている方なら直感的かと思うし。どっちがいいということではなくて、自分が理解しやすい方法で書けばいいと思います、ええ。

※どっちかと言えば各MTタグにidが振られてないとかそういうのが問題かと思う(一回バージョンアップでidが変わったことあるぜ!!)

追記:天野さんから指摘あり。

plugintemplatepath を使う方法は、複数のプラグインで同じことをしようとした時に片方が効かなくなる。

さらに追記

    $app->{ plugin_template_path } =
        File::Spec->catfile( MT->component( 'AwesomePlugin' )->path, 'tmpl' );

    # の代わりに

    $attr = { name => 'include/edit_entry_header.tmpl',
              component => 'AwesomePlugin' };

とする。

Data APIから1日数千件のエントリを更新、新規追加するというような要件の案件で現場から「スピードがでないんっすよ」ということでヘルプ要請が来たのですが、「CGIの起動がクソ重いんじゃないの?」ということで複数件のJSONを纏めて投げられるエンドポイントを作成して対応しようとしたのですが、それでもまっっっったくと言っていいほどパフォーマンスの改善が見られない。再構築をキューにして凌ごうと思ったのですがそれでも一件 Insert するのに10秒も20秒もかかるのです。

  • Data APIで記事インサート、アップデート
  • 1件10〜20秒を要する

調べたところ、こいつが問題。

  • 記事カスタムフィールドが200個近く!

コードを見たり、SQLのクエリを見たり。犯人見つけましたぜ。

MTのカスタムフィールドは1フィールド1レコードで、1フィールド1SQLを発行するのです

なのです。こんなこと知ってたらカスタムフィールド200個近くも作成しないですよね? 5件単位でインサートだったらSQL1000クエリ、カスタムフィールドのためだけに投げるわけです。

なので、MySQLの Bulk Insertを使うようにしてみたらめっちゃ速くなったよというお話でした。単位は秒。Updateの場合は Insertの倍のスピードになります。SQLの数が半端ないので、1台構成より DBとCMSがわかれている複数台構成の方が効果は顕著です。

カスタムフィールドをBulk Insertにしたら300%以上高速化されたという話し(効果測定結果)

ObjectDriver が DBI::mysql で、mt-config.cgiに「BulkInsertMeta」環境変数を指定しているときのみ有効になります。あくまでも実験的な位置づけなのでノンサポート、自己責任でお願いします。

/mt/lib/MT/Meta/Proxy.pm

Oracle や SQL Serverに対応できていないので、その辺りが課題ですが、いずれにしても MTには足回りの強化をお願いしたいところです(既にお願いはしておいたんですがね)。

昨日公開したColorTester、もうお試しいただけましたでしょうか? JIS X-8341-3適合の試験なんかをやっている方は是非お試しください。

ColorTesterスクリーンショット のサムネイル

同様のチェックを行えるツールとして、Colour Contrast Analyser (CCA) というソフトウェアがあります(有名ですね)。

もちろん、既にチェックソフトがあるのに作ったのにはわけがあるのですが、それはそれとして、せっかくなのでメモがてらに色の判別方法について記載しておきます。

W3Cのコントラスト計算アルゴリズム

達成評価についてはW3C(WCAG 2.0)の計算式そのままです。もう、コピペの世界ですね。

検証

チェックポイント

  1. 以下の公式を用いて、各文字(すべて同一ではない限り)の相対輝度を測る:

    • 色の相対輝度 L = 0.2126 * R + 0.7152 * G + 0.0722 * B と定義されている。この場合のR, G 及び B は:

      • RsRGB <= 0.03928 の場合:R = RsRGB/12.92、それ以外の場合: R = ((RsRGB+0.055)/1.055) ^ 2.4

      • GsRGB <= 0.03928 の場合:G = GsRGB/12.92、それ以外の場合:G = ((GsRGB+0.055)/1.055) ^ 2.4

      • BsRGB <= 0.03928 の場合:B = BsRGB/12.92、それ以外の場合:B = ((BsRGB+0.055)/1.055) ^ 2.4

      また、RsRGB, GsRGB, 及び BsRGBは以下のように定義される:

      • RsRGB = R8bit/255

      • GsRGB = G8bit/255

      • BsRGB = B8bit/255

      "^"記号は指数演算子である。

    注意:エイリアス文字では文字の端から2ピクセルの部分の相対輝度の値を使用する。

  2. 同じ公式を用いて、文字のすぐ隣の背景のピクセルの相対輝度を測る。

  3. 次の公式を用いて、コントラスト比を算出する。

    • (L1 + 0.05) / (L2 + 0.05)

      • L1は前景または背景色の明るい方の相対輝度である。及び、

      • L2は前景または背景色の暗い方の相対輝度である。

  4. コントラスト比が4.5:1と同じ、又はそれ以上である。【訳注:この計算式を用いているチェックツールで、コントラスト比が4.5:1以上であることを確認すればよい。】

Colour Contrast Analyserについて特に不満がある訳ではないのですが、以下ことは思っていました。

  • OS X版ではOS標準のカラーピッカーを使うため、カラーピッカーを起動してからディスプレイの色選択まで、1アクション多い(Windows版では直接色を拾えるようになっています)
  • 画像から直接色を拾えないものか。その方が絶対楽だし、大量の画像を一点ずつ色選択してチェックって、数万ページの案件のチェックとかだと現実的に膨大な作業量になる

ColorTesterも標準設定ではOS標準のカラーピッカーを使うようになっていますが、設定→「OS標準のカラーピッカーを使う」チェックボックスをOffにすることで、直接画面から色を拾えるようになります。今のところマルチディスプレイには非対応であることと、Windows版では画面の解像度設定によってうまく色が拾えないことがあります。その場合Colour Contrast Analyserでもずれるんですよね。うまく拾えない場合、Colour Contrast Analyserでもカーソル位置からずれてしまう場合は、画面解像度の設定を変更して試してみてください。

お試しいただけるとわかるかと思うのですが、ブラウザからのドロップやファイルドロップでの色取得、それなりにうまく取得できているように思いませんか? でも、ドキュメントにも書いていますし最初の自動判別時にダイアログが出ることからもお分かりの通り、自動取得した色は正確ではない可能性があります。

色選択の際に最初に表示されるダイアログ

そもそも、2色しか拾えない訳ですから、前景色や背景色が明らかに複数の色で構成されている画像などでは取得に失敗します。明らかに拾い間違っているように見えるときは、あきらめてカラーピッカーから直接色を拾ってください。

ColorTesterはどうやって画像の背景色と前景色を判別しているのか

さて、本題です。
実際、トライ & エラーを繰り返しながら調整していったということなのですが、結果的に現在のバージョンでは以下のような考え方、アルゴリズムで判別を行っています。

  1. 一定以上の大きさの画像は画像サイズを小さくする(処理速度の問題)
  2. 近似色を丸める(グラデーションなどが用いられていると、不必要に色の数が多くなり判別が難しくなる)
  3. 画像のピクセルのカラーを調べて、どの色が何ピクセルを占めているかを調べる(keyに色(16進)、valueにピクセル数という形でマッピングしていきます)
  4. 一番面積の大きい画像が、背景色(これは、ほぼ問題ないかと思います)
  5. 問題は前景色です。まずは、二番目に面積が大きい色を前景色と仮定します。
  6. 背景色と、仮定した前景色のコントラスト差を求めます。これは、W3Cの計算式をそのまま使っています。
  7. コントラストの差が大きい場合(3以上としました)、仮定した前景色で決定です。
  8. コントラストが3以下の場合、面積の大きい順から色を調べて行き、一定のコントラスト差がある色を探します。但し、一定のピクセル以下の面積のもの(1ピクセルがその色だったとして、それが前景色ってことはないでしょう)は除きます。

というわけで上記の処理の結果、背景色と前景色を推測しているというわけです。色を丸めている点で、これはあくまでも近似色である、という見方もできるかと思いますが、どのみちディスプレイの色ってのはデリケートな要素が絡むものです。MacとWindowsでも違う値になるものがあります。Webセーフカラー! 何という懐かしい言葉の響き。ColorTesterでは16進の入力フィールドの背景色に取得した色セットしますから、試験の際に、もし自分の感覚とずれていたら念のためピクセルから取得した色で再チェックをお願いします。

その他のアイデア

正確さもさることながら、いかに効率よく背景色と前景色を判別するか、実装を考える上でいくつかのアイデアを試しました。

中央の1ピクセルを横になめていってはどうか?

上下中央のピクセルをなめる

横書きなら、これは有効なんですけどね。縦書きの画像だと、拾い損ねる可能性がありました。こんな画像だったらいいんですけどね。

斜めではどうか?

斜めにピクセルをなめる

縦書きでも横書きでも、これならクリアできそうですね。ただ、背景色についてはグラデーションかかってたりすると正確に取得できないのですね。また、上下左右の余白が広い場合にうまく行きません。もちろん、こんなべた塗り前景色一色の画像なら判別できるのですが。

他にも、隣接しているかどうかを判断基準に加えたり、ノイズを除去したりといったことも考えられますが、少なくとも「画像化された文字」の判別に特化するなら、あまり気にしなくても良いようです。 まぁ、単純にこういうのって面白いです。良いアイデアや良いアルゴリズムがあれば教えてください。

もう、ずっとなんだけど、今に始まったことではなくて、何年前からだったか継続して続けていることがある。

HTMLTidyGateway

小さなものでもいいから、形にして、リリースすること

数が必要なわけではなくて、一つものをメンテし続けるということでも構わないし、ブログを続けて書くということでも構わない。作りかけのものが100あるより、1つ世に出すことのほうが、結果になってると思うのです。

扱っているテーマが小さなもので、ニッチなものであるからそれが大した話題になったりそれが直接儲けにつながるわけではないけれども、リリースにはそれなりにエネルギーが要る。CPANにモジュールアップしようと思えば、テスト書いたり英語でドキュメント書いたりパッケージにしたりといったエネルギーが要るし、ウェブサービスやサイトにするなら、それなりのデザインも必要です。

人の力を借りなくても何とかなる時代

でも、今やGitHubページでもtumblrでも、もしくは Twitter Bootstrapでもいいんだけど、形を整えるならいくらでも方法はある時代なんだし、サーバー立てるにしてもAWSなんかのクラウド普及で敷居下がってる。コスト面もスキル面も、これまでになく低いスキルとコストでリリースできるようになってると思う。

何故、小さなものか。

別におっきなものでも全然構わんし、でっかいことはいいことだけどさ、忙しいじゃない。そんなに自由にできる時間なんて無いってのが実際の所ですわね。でも、小さなものでも「リリース」にはそれなりのエネルギーが必要で、モノができたら8割、あとリリースの2割の作業には別のスキルやモチベーションが必要なんですよきっと。

何故小さなものをリリースすることを推奨するかってのは、小さなものをリリースするってのは、この、リリースするという別のスキルを鍛えるということにつながるからです。

受託のウェブ制作でもCMS構築でも、リリース時って別のエネルギーが要るじゃないですか。リリースって、そう多くないんだけど(職種や案件規模でも違うと思いますが、少なくとも私たちの会社ではリリースってそんなに頻繁にあるわけじゃなくて、何だか特別なタイミングです)。

これを、普段から、自分のプロジェクトで鍛えておくことで、大きなリリースに耐えられる地力をつけようという意味です。

自分の名のもとに何かをリリースすることで、顧客の気持ちがわかる

小さなものであっても、細かなデザイン崩れやリンク切れとか、エラーとか不具合とか、自分の名のもとにやると、気になるし、嫌じゃんか。なので、リリース後の微調整とかを自然とやるようになる。だから、修正はいっぺんに言ってよ、とか顧客に言う前に、その背景がちゃんと理解できるようになる。細かな修正や、やり直し、手戻りは、最初からあるものですよ。そういうことが、小さなもののリリースによって学べると思うのです。

未サポートです。すいません。こう書いとかないとサポートチームに怒られるし(><。 そのうちMTのパッチなりバージョンアップでサポートされる筈かと。多分。知らん間にPHPがオプション機能扱いになってるけど(あんまり嬉しくないってか、むしろ気に入らないのだが)

ダイナミック コンテンツの生成など、Movable Type のオプション機能を利用したい場合は こちら もご参照下さい。

リンクテキストが「こちら」になっているのは、アクセシビリティ的に宜しくないということを覚えておいてね。

さて、こちらのページ (Movable Type のオプション機能を利用するための環境) には、このような記述があります。

既知の問題があります。 PHP5.3x PHP5.4x 以外のバージョンでは、ダイナミックパブリッシングが利用できません。

先日、ローカルデモ環境のMySQLが壊れて、MAMPを最新版にしたらPHPが5.5になっててエラーが出たのです。焦りました。エラーの内容は、以下。

Deprecated:preg_replace():The/e modifier is deprecated,
use preg_replace_callback instead in
/var/www/cgi-bin/mt/php/extlib/smarty/libs/Smarty_Compiler.class.php on line 270

Speed is everything! 軽量なDynamicMTMLを動かす編。

PHPerのためのMovable Type講座

DynamicMTMLは、元来はhtmlファイル内のMTタグを動的処理するために作られました。平たく言えば、静的生成されたページの一部をMTタグで動的に処理するためのものです。但し、DynamicMTMLはこれにとどまらず、タスクやワーカーを処理したり、config.php形式でプラグインを完結に(まとめて)書けたりといった様々な機能を持っています。

今回は、そのあたりの機能は必要とせず、とにかく軽量、速く処理したいということで、ファイル中のMTタグのみを実行するだけ、しかも不要なプラグインは初期化そのものをスキップしてしまうことで、とにかく高速に動かすことを目的とした.mtview.phpを紹介します。

以下のソースをインデックステンプレートにします。

方針:必要なプラグインのみ初期化する。ブログや記事の情報が不要なら、$blog_idをNULLとして、クエリを一回でも省略する。

前提:必要なphpプラグインを一カ所(以下の例ではCustomHandlers/php)以下に纏めてしまう(もしくは配列で必要なプラグインを複数指定する)。通常の処理の際にファイルの重複があるとエラーになってしまうので、plugins以下ではない場所に設置するか、plugins/CustomHandlers/phplib など、phpフォルダとは別のフォルダにおいておくと良いです。

Aceエディタ

BSD licenseで公開されています。110以上の言語の Syntax highlighting に対応しており、組み込みも簡単。気に入ったのは特に以下。

  • 動作が軽い
  • 自動補完(閉じ括弧とか)
  • 検索・置換に対応(Command(Ctl)+F)
  • タブインデントに対応。複数行を選択して、Tab押下。Shift+Tabキーでタブインデント下げ
  • シンタックス・チェック (警告表示)

シンタックス・チェック(警告表示も)

ドキュメントを見る限りキーバインドのカスタマイズや様々な拡張もできる模様。

PHPでログイン状態のチェックを行う

昨日、ログインの実装についてのコードを紹介しました。最後に宿題? 出しましたけど...

せっかくなのでたまには課題

では、これでログイン処理ができたとして、以降のリクエストのログインチェック、ユーザー情報の取得とコンテクストへのセットはどのようにすればよいでしょうか?

と、いうことで回答編(と、おまけ付き)。

PHPerのためのMovable Type講座

クッキーからのログイン状態のチェックについて

  • クッキーからセッションをチェック
  • セッションの有効期限チェック
  • セッションからユーザーを割り出し、存在するか、もしくは有効なユーザーかチェック
  • 必要に応じて権限のチェック

というような流れになります。

<?php
include('/path/to/mt/php/mt.php');
$mt = MT::get_instance(1, '/path/to/mt/mt-config.cgi');
if ( $session_id = $_COOKIE[ 'mt_commenter' ] ) {
    // セッションをチェック
    require_once( 'class.mt_session.php' );
    $session = new Session;
    $session->Load( "session_id='${session_id}'&session_kind='SI'" );
    if ( isset( $session ) ) {
        $ttl = $mt->config( 'UserSessionCookieTimeout' );
        if ( ( $session->session_start + $ttl ) < time() ) {
            // 有効期限切れ
            $session->Delete();
            echo 'Your session has expired. Please sign in again to comment.';
            return;
        } else {
            $author = $mt->db()->fetch_author_by_name( $session->session_name );
            // ユーザーチェック
            if ( isset( $author ) ) {
                if ( $author->author_status != 1 ) {
                    unset( $author );
                }
            }
        }
    }
}
if (! isset( $author ) ) {
    echo 'Permission denied.';
    return;
}
// 必要に応じて権限(mt_permissionやmt_association)のチェック

コンテキストにユーザーをセットする

こうすることで、MTAuthorFoo タグ、ユーザーカスタムフィールドの値が取得できるようになります。

// $ctx = $mt->context();
// $ctx->stash( 'author', $author );

ログイン処理をPHPで実装してみよう、編。

PHPerのためのMovable Type

DynamicMTMLを使うと簡単に書けますが、今回はDynamicMTMLなしでも動くコードを紹介します。

ログインの処理フロー

  • ユーザー名とパスワードをポストする
  • ユーザー名から mt_author オブジェクトをロード
  • 渡されたパスワードを暗号化してデータベースの値と比較
  • 正しければ、クッキーをセット
  • mt_session レコードにセッション情報を保存

mt_session の session_kind は 'SI' とする。クッキー名は 'mt_commenter'。Session.pm のドキュメントにありますね。

=item SI

Active commenter sessions are held in SI sessions.

Windows AzureはMicrosoft Azureに変わったそうで。まぁ、名前はどうでもいい。

AZBlobDisk

Perl版は無いのですがPHPはSDKがあります。Azureのストレージへのアクセスはトークンの組み立てが面倒なので、それだけでもね。こういうのがあると手軽です。

注意点としては、PEARの HTTP/Request2.php が必要なところですね(Mail_mimeと Mail_mimeDecodeも必要)。

The PHP Client Libraries for Azure have a dependency on the HTTP_Request2, Mail_mime, and Mail_mimeDecode PEAR packages. The recommended way to resolve these dependencies is to install these packages using the PEAR package manager

use WindowsAzure\Common\ServicesBuilder;
require_once 'WindowsAzure/WindowsAzure.php';

$protocol = 'https';
$container_name = 'test';
$connectionString = "DefaultEndpointsProtocol=${protocol};AccountName=${account_name};AccountKey=${primary_access_key}";
$blobRestProxy = ServicesBuilder::getInstance()->createBlobService( $connectionString );

 // 一覧取得
$blob_list = $blobRestProxy->listBlobs( $container_name );
$blobs = $blob_list->getBlobs();

 // Blobの取得
$blob = $blobRestProxy->getBlob( $container_name, 'path/to/blob' );
ob_start();
fpassthru($blob->getContentStream());
$content = ob_get_contents();
ob_end_clean();
file_put_contents( 'path/to/out', $content );

// Blobの生成(アップロード)
$blobRestProxy->createBlockBlob( $container_name, 'path/to/new_blob', $content );

PHPerのためのMovable Type

ちょっと間空きましたけど、続きます。PHPでワーカー/タスクを作る方法。

DynamicMTMLでタスク/ワーカーを作る

config.php によるDynamicMTML対応プラグインを作ります。詳しくはその11を参照。

タスクとワーカーの違いは、

  • ワーカー = データベースに保存されたキューを実行する
  • タスク = キューの有無にかかわらず定期的にコードを実行する

キューを登録するのは主にPerl側のプラグインになるでしょう。こちらの作成方法はMTのドキュメントを参考に。

通常はplugins/Foo/Worker/WorkerFoo.pm を作って登録することになります。

// plugins/Foo/php/config.php

<?php
class Foo extends MTPlugin {
    var $app;
    var $registry = array(
        'name' => 'Foo',
        'task_workers' => array(
              'worker_foo' => array(
                            'label' => 'Label of Foo',
                            'code'  => 'worker_foo',
                            'class' => 'Foo::Worker::WorkerFoo', ),
        ),
    );
    function worker_foo ( &$app, $jobs ) {
        $mt = MT::get_instance();
        foreach ( $jobs as $job ) {
            $meta = $job->arg;
            $meta = preg_replace( "/^.*(SERG)/", '$1', $meta );
            $meta = $mt->db()->unserialize( $meta );
            // ワーカーの処理
        }
    }
}

第二引数 $jobs に、キューに保存されたジョブの配列が渡されるので、これに処理を書いて行けば良いです。$job->arg にシリアライズされたデータが入っているケースがありますが、$mt->db()->unserialize( $meta ); で配列に割り戻せます。

動かない(違うブログのが出てくるねん)ってのが来たのですよね。ずいぶん昔に公開したものなんですけど。気が抜けないっす。

おかしいのは動的にAltTemplatePath(代替テンプレートパス)をセットしているところ。

        my $plugin_tpldir = File::Spec->catdir( $plugin->path, 'tmpl', $tpl_directory );
        $app->config( 'AltTemplatePath', $plugin_tpldir );

何でだろう、と小一時間悩んだのですが、Core.pmを眺めていて、おやっと。

        'AltTemplatePath' => {
            default => 'alt-tmpl',
            path    => 1,
            type    => 'ARRAY',
        },

あれー(いや、ARRAY...)

my @paths = $app->config( 'AltTemplatePath' );
use Data::Dumper;
MT->log( Dumper \@paths );

# $VAR1 = [ '/Applications/MAMP/htdocs/app/alt-tmpl', '/Applications/MAMP/htdocs/app/plugins/CMSContext/tmpl/Blog_1' ];

あ。。。

PHPerのためのMT講座

DynamicMTML編その2。データベース接続を簡単に(ORマッパー)。

PHPからMTのデータベースに接続する方法について、これまでに class MTDatabase を使う方法、class BaseObject を使う方法について紹介しましたが、DynamicMTMLを利用することで、SQLを書かずにMTのデータベースオブジェクトにアクセスすることができます。

これは、MTの心臓部とも言えるPerlの MT::Object をPHP化する目的で作られた(というか、作った)もので、完全な互換性はありませんが、プラグインを書く人にとってはスタティックパブリッシング(Perl)、ダイナミックパブリッシング(PHP)の両方で同じ書き方ができるメリットがあります。

また、「SQLを文字列操作(連結とか)で組み立てるな」って話しもあって、そういう面からもこれを使うメリットがあるかと思います。

プラグインから class DynamicMTMLを取得する

方法は2つ。

global $app;

とするか、

$app = $ctx->stash( 'bootstrapper' );

これで、class DynamicMTMLが取得できます。

PHPerのためのMovable Type

DynamicMTML編。

DynamicMTMLは、Movable Typeのaddonで、PHPによるダイナミックパブリッシングを拡張するフレームワークです。

ドキュメントはかなり詳しく書いたつもりなので詳細はそちらを見ていただくとして、簡単に言うと、以下のようなことが実現できます。

  • スタティックなHTMLファイルの中のMTML(MTタグ)をダイナミックに処理できる
  • ダイナミック処理を前提とした様々なMTタグ(ログイン判別、UA判別)
  • コールバックプラグインを作成することができる(処理の割り込み、カスタマイズ)
  • MT::Object ライクなORマッパ
  • class MTPluginによる、プラグイン作成支援
  • PHPによるタスク、キューの実行

等々。DynamicMTMLは単なるスタティックなHTMLファイルの中のMTML実行エンジンではありません。

ブログをまたがったカテゴリ串刺しアーカイブをよりスマートに実現する

前々回紹介した「ブログをまたがったカテゴリ串刺しアーカイブ」ですが、DynamicMTMLを使うことで、よりスマートに実装できるようになります。

  • 前回は、/category.php/movabletype/ のようなURLでしたが、素直に /movabletype/ 等のパスで動作させられるようになります。
  • /movabletype/template/ などのようにサブカテゴリにも対応します。
  • 最上位のブログ(ウェブサイト)にカテゴリが存在する必要があります。
  • プラグインファイル一つに様々な処理を集約できます(複数のテンプレートタグも、コールバックプラグインもひとまとめにできます)
  • テンプレートをヒアドキュメントに書くのではなく、MTのテンプレートとして管理できるようになります。テンプレートとして管理し、パブリッシュしたファイルを出力テンプレートにする、ということです。

プラグインの作成

実は、ここでやろうしていることを少し前にPowerCMSのブログに書きました。以下の記事です。

また、DynamicMTMLの config.php 形式でのプラグイン作成方法は下記の記事で紹介しています。

今回、コードは以下にUPしました。

構造とポイントのみ下記に書きます。

$MT_DIR/
|__ plugins/
   |__ EntriesFromRSS/
      |__ php/
         |_config.php

$registryプロパティに配列でコールバックやタグを登録します。複数のタグや処理も一つのファイルの中にスッキリと書くことができます。

<?php
require_once( 'MTUtil.php' );
class EntriesFromRSS extends MTPlugin {
    var $registry = array(
        'config_settings' => array(
            'RewriteArchiverRewriteTo' => array( 'default' => '/archiver.html' ),
        ),
        'callbacks' => array(
            'post_init' => 'rewrite_archiver',
        ),
        'tags' => array(
            'block' => array(
                'entriesfromrss' => '_hdlr_entriesfromrss',
            ),
        ),
    );
    function rewrite_archiver ( $mt, &$ctx, &$args ) {
        // 初期化後に割り込み処理を入れる
        // URLを見て、カテゴリベースネームに合致したら$ctxをセットし、
        // URLを騙して(/archiver.html)に騙して処理する
    }
    function _hdlr_entriesfromrss  ( $args, $content, &$ctx, &$repeat ) {
        // ブロックタグの処理を書く
    }
}
?>

Facebook

Twitter

このアーカイブについて

このページには、過去に書かれたブログ記事のうちプログラミングカテゴリに属しているものが含まれています。

前のカテゴリはアクセシビリティです。

次のカテゴリは書評などです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 6.2.6