« 目次

Movable Type オブジェクト・リファレンス

MT::Object

概要

MT::Objectのサブクラスは以下のように作成します。

package MT::Foo;
use strict;

use MT::Object;
@MT::Foo::ISA = qw( MT::Object );
__PACKAGE__->install_properties({
    columns => ['id', 'foo',],
    indexes => {
        foo => 1,
    },
    datasource => 'foo',
});

作成したMT::Objectのサブクラスは、以下のように使います。

use MT;
use MT::Foo;

## Create an MT object to load the system configuration and
## initialize an object driver.
my $mt = MT->new;

## Create an MT::Foo object, fill it with data, and save it;
## the object is saved using the object driver initialized above.
my $foo = MT::Foo->new;
$foo->foo('bar');
$foo->save
    or die $foo->errstr;

解説

MT::Objectは、何らかの方法(DBMファイル、リレーショナル・データベースなど)でシリアライズ/保存するすべてのオブジェクトのベースとなるクラスです。

オブジェクト自体は、データの保存方法について、いっさい関知しません。オブジェクトが知ることができるのは、そのオブジェクト自体がどのようなデータで構成されているか、またそれらのデータにどのような名前(カラム名)がつけられているかといったことだけです。実際の保存メカニズムは、MT::ObjectDriverクラスと、各ドライバーに対応したサブクラス内で定義されています。一方、MT::Objectのサブクラスは、標準的なメモリー上のPerlオブジェクトですが、より詳細な情報を持っています。

このように保存データとメモリ上のデータを分けることにより、オブジェクトをさまざまな方法でディスク上にシリアライズして保存することができます。たとえば、同じオブジェクトがMySQLデータベースやDBMファイルといった違った形で保存できます。保存方法の追加は、オブジェクト・ドライバーを書くのと同じくらいシンプルにできます。もちろんこれ自体、慎重さを要する作業ですが、少なくともMovable Typeの他の部分のコードを書き換える必要はありません。

サブクラスの作成

MT::Objectのサブクラスの作成は非常に簡単で、作成するオブジェクトのプロパティとメタデータを定義するだけです。まず、クラスを定義し、MT::Objectからの継承を記述します。

package MT::Foo;
use strict;

use MT::Object;
@MT::Foo::ISA = qw( MT::Object );

次にクラス名からinstall_propertiesメソッドを呼び出します。クラス名は、特殊変数__PACKAGE__を使って簡単に取得できます。

__PACKAGE__->install_properties({
    columns => ['id', 'foo',],
    indexes => {
        foo => 1,
    },
    datasource => 'foo',
});

install_propertiesメソッドは、新規クラスのメタデータをMTシステムに設定するための処理を行います。このメソッドの引数は1つで、作成するクラスのメタデータを格納したハッシュ・リファレンスです。このハッシュ・リファレンスには以下のキーが指定可能です。

columns

カラム(フィールド)名を定義します。カラム名はメソッド名としても使いますので、不正な文字を使ってはなりません(また、カラム名はリレーショナル・データベース・テーブルのカラム名の一部として使われることもあります。これも不正な文字を使ってはならないもう一つの理由です)。

columnsキーに対応する値は、カラム名を格納した配列へのリファレンスとして指定する必要があります。

indexes

カラムのインデックスを指定します。これは、リレーショナル・データベースのように、データストア側でインデックスを自動的に管理してくれない一部のオブジェクト・ドライバー(DBMなど)でのみ意味を持ちます。

indexesキーに対応する値は、インデックスを必要とするカラム名をキーとし、それぞれの値を1とするハッシュへのリファレンスでなければなりません。

注意:DBMドライバーを使っている場合、カラム名に対してインデックスを指定しないと、loadインターフェースやload_iterインターフェースで、そのカラムに合致する値を持つオブジェクトを選択することができません。

audit

作成するクラスに、記録機能を自動的に追加します。各オブジェクトにcreated_oncreated_bymodified_onmodified_byの4つのカラムが追加されます。これらのカラムには適切な値が自動的に挿入されます。

注意:created_bymodified_byは、現在使われていません。

datasource

データソース名を指定します。このデータソース名はクラスを一意に特定する名前で、オブジェクト・ドライバーが、テーブル名やファイル名などの作成時に使用します。したがって、特定のドライバーに固有の名前を指定してはなりません。

利用方法

システム初期化

MT::Objectクラスとそのオブジェクトを使用(読み込み、保存、削除)する前に、必ずMovable Typeシステムを初期化してください。以下のコードで初期化を行うことができます。

use MT;
my $mt = MT->new;

新規MTオブジェクトを生成することにより、環境設定ファイルから設定値が読み込まれ、シリアライズしたオブジェクトの管理に用いるオブジェクト・ドライバーが初期化されます。

新規オブジェクトの作成

MT::Objectクラスの新規オブジェクトを作成するには、newメソッドを使います。

my $foo = MT::Foo->new;

newメソッドには引数はありません。メモリー上のオブジェクトを初期化するだけです。このオブジェクトはディスクに保存する必要もありません。純粋にメモリー上のオブジェクトとして使うことができます。

カラム値の設定と取得

オブジェクトのカラム値を設定するには、カラム名をメソッド名として使い、そのカラムに設定する値を引数として渡します。

$foo->foo('bar');

上の例の戻り値は"bar"で、これはfooというカラムに設定した値です。カラムにすでに設定されている値を読み取るには、このメソッドを引数なしで呼び出します。

$foo->foo

上記の例は、$fooオブジェクトのfooというカラムの値を返します。

オブジェクトの保存

オブジェクト・ドライバーを使ってオブジェクトを保存するには、次のようにsaveメソッドを呼び出します。

$foo->save;

保存が成功すると、saveメソッドは真となる何らかの値を返します。失敗した場合にはundefを返します。このとき、同じオブジェクトのerrstrメソッドを呼び出すと、エラー・メッセージが取得できます。

$foo->save
    or die "Saving foo failed: ", $foo->errstr;

複数のオブジェクトをループで保存する場合、オブジェクトのロックについての項を参照してください。

既存のオブジェクトの読み込み

loadメソッドを使って、データストアからオブジェクトを読み込むことができます。loadメソッドは間違いなく最も複雑なメソッドです。というのは、オブジェクトの読み込みには、IDから、カラム値から、他のタイプのオブジェクトとの連結から、といったさまざまな方法があるからです。

さらに、オブジェクトの読み込みには、一括して配列に読み込む方法(load)と、反復子を使って一つずつオブジェクトを読み込む方法(load_iter)があります。loadの一般的な書式は次のとおりです。

my @objects = CLASS->load(\%terms, \%arguments);

load_iterの一般的な書式は次のとおりです。

my $iter = CLASS->load_iter(\%terms, \%arguments);

引数は両メソッド共通で、条件に合うオブジェクトの返し方が違うだけです。スカラー・コンテキストでloadメソッドを呼び出すと、@objects配列の最初の要素だけが返されます。IDからオブジェクトを読み込む場合など、返ってくるオブジェクトが1つしかないことがわかっている場合には、この方法が使えます。

\%termsは、以下のいずれかの形式で指定する必要があります。

  • データストア中のオブジェクトの数値ID。

  • カラム名をキーとし、そのカラムの内容を値とするハッシュへのリファレンス。たとえば、fooというカラムの内容が"bar"と一致するようなMT::Fooオブジェクトを読み込むには、loadを次のように呼び出します。

    my @foo = MT::Foo->load({ foo => 'bar' });

    このハッシュの値には、スカラー値以外に、配列へのリファレンスも指定できます。\%argumentsリストのrangeパラメーターと組み合わせることで、範囲を指定した検索が可能です。ハッシュの値にリファレンスを指定する場合、配列の最初の要素には指定したい範囲の下限を指定し、2番目の要素には上限を指定します。

\%argumentsは検索パラメーターを格納したハッシュへのリファレンスでなければなりません。このハッシュには、以下のパラメーターが指定可能です。

sort => "column"

見つかったオブジェクトを、"column"という名前のカラムの内容でソートします。"column"はインデックスつきのカラムでなければなりません(上記indexesを参照してください)。

direction => "ascend|descend"

ソート順の指定で、sortと組み合わせて使います。"ascend"(昇順)か"descend"(降順)のいずれかが指定できます。デフォルトは"ascend"です。

limit => "N"

limitの指定がない場合には、デフォルトで条件に合うオブジェクトをすべて読み込みますが、limitを指定すると、オブジェクトの個数の上限をN個にして読み込みます。

offset => "M"

limitと組み合わせて使い、最初のN個を返す(offsetの指定がない場合のデフォルト)かわりに、M番目からN個分のオブジェクトを返します。

start_val => "value"

limitsortの両パラメーターと組み合わせて使います。条件に合う最初のN個のオブジェクトを返す代わりに、"column"(ソート基準となるカラム)の値が"value"より大きい最初のN個分のオブジェクトを返します。

range

引数\%termsの中で、あるカラム名に対する値として配列リファレンスを指定した場合に、これと組み合わせて使います。指定したカラムの値が、ある値に合致するオブジェクトを検索する代わりに、値が指定した範囲内にあるオブジェクトを返すように指示します。

rangeの値には、ハッシュ・リファレンスを指定します。このハッシュのキーは、範囲として解釈すべきカラムのカラム名で、値はすべて1です。

join

別のオブジェクトの集合を検索基準あるいはソート基準として、オブジェクトの集合を選択するのに使います。たとえば、最近コメントのついたN個のエントリーを選択する場合などです。この場合、検索基準はMT::Commentオブジェクトですが、返されるオブジェクトはMT::Entryオブジェクトです。このような場合にjoinを使うと、最近のMT::Commentオブジェクトを読み込んでから、それぞれのコメント先であるMT::Entryオブジェクトを個別に読み込むより、処理速度が高速になります。

このjoinは、SQL文のJOIN演算子とは異なり、返されるオブジェクトが常に1つの型のオブジェクトだけであることに注意してください。上の例では、返されるオブジェクトはMT::Entryオブジェクトだけです。MT::Commentオブジェクトのカラムを含めることはできません。

joinの一般的な指定方法は以下のとおりです。

join => [ CLASS, JOIN_COLUMN, I<\%terms>, I<\%arguments> ]

CLASSは結合に使うクラスです。*JOIN_COLUMN*は2つのオブジェクトのテーブルを結合するカラム名です。\%terms\%argumentsの指定方法は、外側のloadまたはload_iterメソッドの引数と同様で、結合に利用するオブジェクトの選択方法を指定します。

たとえば、次の文のようにloadメソッドを使うと、最近コメントがついた10件のエントリーを選択することができます。

my @entries = MT::Entry->load(undef, {
    'join' => [ 'MT::Comment', 'entry_id',
        { blog_id => $blog_id },
        { 'sort' => 'created_on',
        direction => 'descend',
        unique => 1,
        limit => 10 } ]
});

この文では、*unique*を指定することにより、返されるMT::Entryオブジェクトに重複がないことが保証されています。このフラグの指定をしない場合、同一のエントリーに2つのコメントがついていると、同じMT::Entryが2つ返されることになります。

unique

返されるオブジェクトが一意であることを保証します。

この指定が意味を持つのは、joinを使うときだけです。というのは、単一のオブジェクトのデータストアからデータを読み込む場合には、返されるオブジェクトは常に一意だからです。

オブジェクトの削除

データストアからオブジェクトを削除するには、次のように、loadメソッドで読み込んだオブジェクトからremoveメソッドを呼び出します。

$foo->remove;

removeメソッドは、削除に成功すると、真となる何らかの値を返します。失敗した場合にはundefを返します。このとき、同じオブジェクトのerrstrメソッドを呼び出すとエラー・メッセージが取得できます。

$foo->remove
    or die "Removing foo failed: ", $foo->errstr;

ループ内で複数のオブジェクトを削除する場合には、オブジェクトのロックについての項を参照してください。

特定のクラスに属する全オブジェクトを一括削除する

特定のクラスに属するすべてのオブジェクトを一括で削除するには、次のように、該当するクラス名からremove_allメソッドを呼び出します。

MT::Foo->remove_all;

remove_allメソッドは、削除に成功すると、真となる何らかの値を返します。失敗した場合にはundefを返します。このとき、同じクラスのerrstrメソッドを呼び出すとエラー・メッセージが取得できます。

MT::Foo->remove_all
    or die "Removing all foo objects failed: ", MT::Foo->errstr;

複数のオブジェクトの個数を取得する

一定の条件に合うオブジェクトがいくつあるかを調べるには、クラス名からcountメソッドを呼び出します。

my $count = MT::Foo->count({ foo => 'bar' });

countメソッドは、前述のloadload_iterと同じ引数(\%terms\%arguments)を取ります。

オブジェクトがデータストア中に存在するかどうかを判別する

あるオブジェクトがデータストア中に存在するかどうかを調べるには、オブジェクト名からexistsメソッドを呼び出します。

if ($foo->exists) {
    print "Foo $foo already exists!";
}

オブジェクトをグループ化してその個数を調べる

count_group_byメソッドは、SQLベースのオブジェクト・ドライバーを使っている場合に、指定したカラムのすべての値から重複を取り除いたリストを取得し、それぞれの値について、その値を持つオブジェクトがいくつあるかを数えます。このメソッドには複数のカラムを指定することもできます。その場合には、指定したカラムの対(またはnカラムの組み合わせ)を単位で重複しないものの値のリストと、それぞれのオブジェクト個数を返します。さらに、強力な使い方として、カラム名の代わりに任意のSQL表現を指定し、そのSQL表現の実行時にどのような値を返すオブジェクトがいくつあるかを調べることもできます。

$iter = MT::Foo->count_group_by($terms, {%args, group => $group_exprs});

引数$termsと%argsは、他のメソッドと同じように、MT::Fooオブジェクトの中から条件に合う集合を抽出するための指定です。$group_expressionsはグループ化の基準となるSQL表現を格納した配列リファレンスです。$group_expressionsの結果として得られる重複しない値の組み合わせ1つにつき、1つの行が返されます。たとえば、$group_expressionsに単一のカラムを指定した場合(例:group => ['created_on'])、created_onカラムの値として重複しないもの1つにつき1行の結果が返されます。$group_expressionsが複数のカラムの場合には、指定したカラムにある重複しない値の対(または組み合わせ)1つにつき、1行が返ります。

反復子$iterを1回実行するごとに、次のような形のリストが返ります。

($count, $group_val1, $group_val2, ...)

ここで$countは、$group_expressionsで指定したカラムの値が($group_val1, $group_val2, ...)であるMT::Fooオブジェクトの個数となります。$group_val1, $group_val2は、引数$group_exprsに指定したのと同じ順番になります。

次の例では、MT::Pipオブジェクトを、cat_idとinvoice_idでグループ化し、この二つの値の組み合わせの各々について、それらの値を持つオブジェクトの個数を出力します。

$iter = MT::Pip->count_group_by(undef,
    {group => ['cat_id',
    'invoice_id']});
while (($count, $cat, $inv) = $iter->()) {
    print "There are $count Pips with " .
    "category $cat and invoice $inv\n";
}

オブジェクトの状態を調べ、操作する

オブジェクトのフィールドの値を一括して取得したり設定したりするには、それぞれcolumn_valuesメソッドとset_valuesメソッドを使います。前者は、以下の例のように呼び出すことで、カラム名から値へのマッピングを行うハッシュ・リファレンスを返します。

$values = $obj->column_values()

set_valuesは、同様のハッシュ・リファレンスを引数として受け取りますが、すべてのカラムの値を指定する必要はありません。たとえば、

$obj->set_values({col1 => $val1, col2 => $val2});

と呼び出しても、次のように個別に呼び出しても結果は同じです。

$obj->col1($val1);
$obj->col2($val2);

その他のメソッド

$obj->clone()

$objの複製、つまり、すべてのフィールドについて全く同じ値を持つ別のオブジェクトを返します。別のオブジェクトなので、一方のオブジェクトの値を変更しても、他方のオブジェクトには影響しません。

$obj->column_names()

$objのカラム名のリストを返します。このカラム名には、install_propertiesメソッドで指定したカラム名も含まれます。また、install_propertiesメソッドでauditを有効にした場合には、記録用のカラム(created_on、modified_on、created_byおよびmodified_by)も含まれます。

MT::Foo->driver(), $obj->driver()

オブジェクトとデータベースをリンクするObjectDriverオブジェクトを返します。

$obj->created_on_obj()

オブジェクトが最初にデータベースに保存された時間を格納したMT::DateTimeオブジェクトを返します。

MT::Foo->set_by_key($key_terms, $value_terms)

引数$key_termsに合うオブジェクトを読み込み、そのフィールドのすべて、または一部の値を、引数$value_termsにしたがって設定する簡易メソッドです。たとえば、

MT::Foo->set_by_key({name => 'Thor'},
    {region => 'Norway', gender => 'Male'});

のように呼び出すと、nameフィールドが"Thor"と一致するMT::Fooオブジェクトを読み込み、そのオブジェクトのregionおよびgenderフィールドの値を、指定した値に設定します。

引数$key_termsには、複数の条件を指定することもできます。その場合、指定した条件のすべてを満たすオブジェクトが対象となります。

このメソッドは、指定した条件に合うオブジェクトが1つしかないことがわかっている場合にのみ有用です。$key_termsに指定したカラム名に、データベース上で一意制約(unique指定)がついている必要はありませんが、この条件に合うオブジェクトが1つしかないという確証がなければなりません。

MT::Foo->get_by_key($key_terms)

引数$key_termsに合うオブジェクトを読み込む簡易メソッドです。条件に合うオブジェクトが見つからない場合には、新規オブジェクトを生成し、$key_termsに指定した値を設定したものが返されます。したがって、条件に合うオブジェクトがすでに存在するかどうかにかかわらず、指定した条件のオブジェクトが返されます。ただし、新規オブジェクトを生成した場合、そのオブジェクトは自動的には保存されないので注意してください。

my $thor = MT::Foo->get_by_key({name => 'Thor'});
$thor->region('Norway');
$thor->gender('Male');
$thor->save;

見つからない場合には新規オブジェクトが返されるため、次のような処理を簡略化することができます。

my $obj = MT::Foo->load({key => $value});
if (!$obj) {
    $obj = new MT::Foo;
    $obj->key($value);
}

これは次のように書いても同じです。

my $obj = MT::Foo->get_by_key({key => $value});

自動的にオブジェクトを生成してほしくない場合には、loadメソッドを使ってください。

引数$key_termsには、複数の条件を指定することもできます。その場合、指定した条件のすべてを満たすオブジェクトが対象となります。

このメソッドは、指定した条件に合うオブジェクトが1つしかないことがわかっている場合にのみ有用です。$key_termsに指定したカラム名に、データベース上で一意制約(unique指定)がついている必要はありませんが、この条件に合うオブジェクトが1つしかないという確証がなければなりません。

注意事項

オブジェクトのロックについて

データストアからオブジェクトを読み込む際、そのオブジェクトを含むテーブルには共有ロックがかかります。書き込みの際は、そのオブジェクトを含むテーブルに排他ロックがかかります。

したがって、反復子からオブジェクトを読み込むのと同じループで、オブジェクトの保存または削除を行おうとしても、失敗します。これは、反復子からオブジェクトを読み込んでいる間は共有ロックがかかり続けており、その状態で保存または削除のために排他ロックをかけようとすると、デッドロックが発生するからです。

たとえば、次のようなコードは動作しません。

my $iter = MT::Foo->load_iter({ foo => 'bar' });
while (my $foo = $iter->()) {
    $foo->remove;
}

正しくは、次のようなコードを書く必要があります。

my @foo = MT::Foo->load({ foo => 'bar' });
for my $foo (@foo) {
    $foo->remove;
}

あるいは次のように書くこともできます。

my $iter = MT::Foo->load_iter({ foo => 'bar' });
my @to_remove;
while (my $foo = $iter->()) {
    push @to_remove, $foo
    if SOME CONDITION;
}
for my $foo (@to_remove) {
    $foo->remove;
}

削除したいMT::Fooオブジェクトが、fooが"bar"と一致するものすべてではない場合には、2番目の方法が有効です。削除したいMT::Fooオブジェクトが、一度にメモリーに読み込まれるのは、削除しようとしているMT::Fooオブジェクトだけなので、メモリーが節約できるからです。

コールバック

MT::Objectに関する操作では、たいていの場合、プラグイン・コードへのコールバックを引き起こすことができます。データベース・レコードに変更があった場合に通知を行ったり、データベースにデータが書き込まれるときに前処理や後処理を行ったりしたい場合に特に便利です。

コールバックを追加するには、次のように、MT::Objectのサブクラスからadd_callbackメソッドを呼び出します。

MT::Foo->add_callback("pre_save", <priority>, 
<plugin object>, \&callback_function);

第1引数はフック・ポイントの名前です。MT::Objectのサブクラスにはすべて、次の処理についてpre_およびpost_のフック・ポイントがあります。

  • load(load_iterではloadのコールバックが呼び出されます)
  • save
  • remove
  • remove_all

第2引数<priority>には、コールバックが呼び出される相対的な優先順位を指定します。1から10までの値が指定できます。優先順位が1のコールバックは2よりも先に呼び出され、2のものは3より先に呼び出される、といった要領です。

最初に実行すべきプラグインには0、最後に実行すべきプラグインには11が指定できます。優先順位が0のコールバックは他のどのコールバックよりも先に実行され、複数のコールバックが優先順位0である場合にはエラーになります。優先順位11も同様で、この優先順位を持つコールバックは最後に実行されます。

どの優先順位が特殊順位なのか、覚え方を以下にあげておきます。ご存じのとおり、たいていのギター・アンプには1から10までのボリュームつまみがついていますが、一部のロック・スターのアンプのように、我々のアンプは11まで出すことができます。優先順位11のコールバックは一番うるさいあるいは最強のコールバックで、オブジェクトをデータベースに保存する直前(pre_コールバックの場合)あるいはオブジェクトが返される直前(post_コールバックの場合)に呼び出されます。優先順位0のコールバックは、あとに続くすべてのコールバックに圧倒されるため、一番静かなコールバックといえます。自分のプラグインが他のプラグインと衝突しないで動作するようにしたいときには、この優先順位にすることを検討してみてください。正しい優先順位を決めるには、自分のプラグインを他のプラグインとの関係で考え、そのプラグインを最適な形でユーザーが使えるように、経験に即して優先順位を調節する必要があります。

引数<plugin object>には、プラグインについての何らかの情報を持つMT::Plugin型のオブジェクトを指定します。これはエラー・メッセージ中に、該当するプラグインの名前を表示するために使います。

引数<callback function>は呼び出されるサブルーチンのコード・リファレンスです。この関数に渡す引数は処理内容によって異なります(詳細はMT::Callbackを参照してください)が、どの場合でも第1引数はMT::Callbackオブジェクト自身です。

sub my_callback {
    my ($cb, ...) = @_;

    if ( <error condition> ) {
        return $cb->error("Error message");
    }
}

厳密に言えば、コールバックの戻り値は無視されます。MT::Callbackオブジェクト(ここでは$cb)のerror()メソッドがMTのログにエラー・メッセージを記録します。

もう一つのエラー処理方法は、dieを呼び出す方法です。コールバックがdieすると、MTはログにエラー警告を書き出しますが、MT::Objectの処理は継続されますので、他のコールバックは実行され、データベース操作が発生します。

クラス共通コールバック

次のように、*::で始まるフック・ポイントを指定してコールバックを追加した場合、

MT->add_callback('*::post_save', 7, $my_plugin, \&code_ref);

このコールバックは、post_saveコールバックが呼び出されたときに、必ず呼び出されます。クラス共通のコールバックは、クラス別のコールバックがすべて呼び出された*後に*呼び出されます。クラス共通のコールバックを追加する場合には、MT::Objectのサブクラスではなく、MTクラスのadd_callbackを呼び出す必要があります。

警告

エラー処理には注意が必要です。データベースからのデータの読み書きに際してデータを加工する場合で、コールバックが失敗する可能性がある場合、データは未定義のまま保存される可能性があります。こうなると、ユーザーが当該データを復旧するのは困難になるか、最悪の場合、不可能になってしまいます。


Copyright © 2001-2006 Six Apart, Ltd. All Rights Reserved.