22February
AppObjectでHasManyとか
久々のEthnaネタです。
まだいくつかネタがあることが判明しましたので、徐々に公開していこうと思います。
AppObjectでRailsのActiveRecordでいうBelongsTo/HasOne/HasMany/HasAndBelongsToMany/HasManyThroughを実装してみました。
HasManyThroughはすごく不完全のようです。
AppObject再編が騒がれているこのタイミングというのも何かの縁でしょう。完全にその再編で思い出しただけなんですが。
ただ、だいぶ昔のバージョンでしか試していないのが若干不安です、しかし一応世の中に出しているサービスで稼働中の実績もあります。
この辺の名前は出していいのかわからないので、いったん自重しておきます。
http://bobpp.as-roma.com/svn/ethnaLibs/trunk/projBop_AppObject.php
こちらになります。ほかの機能もいくつか混ざっているようですが、それは追って紹介していこうと思います。
使い方は続きをどうぞ。
また、利用するすべてのAppObject/AppManagerをこのクラスのサブクラスにしてください。
AppManager/AppObjectをフル活用しているエンジニアの大半は <AppID>_AppObjectや<AppID>_AppManagerを持っているという噂なので、そのアプリケーション固有の基底クラスをprojBop_AppObject, projBop_AppManagerに置き換えるだけです。
ということで、ありがちな例としては、
例として、「ユーザはプロフィールを持つ」という関係を書いてみようと思います。
まずは、User#id=1のプロフィールオブジェクトを取得する例です。
そのgetメソッドで関連オブジェクトが取得できます。
では、Profileからユーザを取得する場合は?
こちらも逆にProfileからUserをセットする場合は?
今回は、「ユーザの出身都道府県」という関係を。まずは定義から。
逆に、User#id=2の都道府県を取得するには、
今回は「性別が男 (gender = M) であるユーザを生年月日若い順(ORDER BY birthday ASC)に10件(LIMIT 10)」という検索条件を加えてみます。
書き方としては、AppManager#getObjectListのfilter等と同じなので取っ付きやすいと思います。
新しく作ったユーザをPref#id=2に所属させてみましょう。
逆のやり方としては、
「ユーザへのタグ付け」という例を。まずは、定義からですが、
ユーザとタグの関係を表現するテーブルを tag_user テーブルとして、
relationはこの二つのリレーションを表すテーブルです。[id, user_id, tag_id]のみのテーブルとなります。
from_keyはrelationテーブルのカラムの中で、自身の外部キーが入るカラム名を指定してください。
dest_keyはrelationテーブルのカラムの中で、相手の外部キーが入るカラム名を指定してください。
なお、上記三つも省略できます。from_key, dest_keyはBelongsTo,HasOne,HasManyのkeyと同じ命名規則です。
relationは、二つのモデル名を比較して小さい方を前方に置いた、モデル名_モデル名 となります。
よって、上の例も省略記法を用いると、
Tag#id=1がついたユーザ群を取得するには、
Tag#id=1がついているUser#id=2を取得する場合は、
User#id=1にTag#id=2をつけてみましょう。
HasAndBelongsToManyにはfindがありません。いずれ実装されるのかもしれません。。。?
では、「ユーザは複数のグループに所属する」という関係を扱ってみましょう。まずは定義ですが、
所属する関係をmembershipテーブルで表現し、
単純にUser#id=1が所属するグループを取得するだけであれば、
RelationObjectというプロパティ名は、現在は固定です。
User#id=1をGroup#id=4に保存してみましょう。
これでAppObjectでサクサク開発していただければと思います。
バグとかもしございましたら、ご連絡ください。
まだいくつかネタがあることが判明しましたので、徐々に公開していこうと思います。
AppObjectでRailsのActiveRecordでいうBelongsTo/HasOne/HasMany/HasAndBelongsToMany/HasManyThroughを実装してみました。
HasManyThroughはすごく不完全のようです。
AppObject再編が騒がれているこのタイミングというのも何かの縁でしょう。完全にその再編で思い出しただけなんですが。
ただ、だいぶ昔のバージョンでしか試していないのが若干不安です、しかし一応世の中に出しているサービスで稼働中の実績もあります。
この辺の名前は出していいのかわからないので、いったん自重しておきます。
Source
ついに、こういうライブラリもsvn管理になりました。http://bobpp.as-roma.com/svn/ethnaLibs/trunk/projBop_AppObject.php
こちらになります。ほかの機能もいくつか混ざっているようですが、それは追って紹介していこうと思います。
使い方は続きをどうぞ。
How to Load.
libsディレクトリなどに置いてください。include_onceできれば別にどこでもかまわないです。また、利用するすべてのAppObject/AppManagerをこのクラスのサブクラスにしてください。
AppManager/AppObjectをフル活用しているエンジニアの大半は <AppID>_AppObjectや<AppID>_AppManagerを持っているという噂なので、そのアプリケーション固有の基底クラスをprojBop_AppObject, projBop_AppManagerに置き換えるだけです。
ということで、ありがちな例としては、
include_once 'libs/projBop_AppObject.php';
class AppId_AppObject extends projBop_AppObject { ... }
class AppId_AppManager extends projBop_AppManager { ... }
このような形でしょうか。has_one/belongs_to
早速has_one/belongs_toから。例として、「ユーザはプロフィールを持つ」という関係を書いてみようと思います。
class AppID_User extends projBop_AppObject {
var $has_one = array(
'profile' => array(
'key' => 'user_id',
),
);
...
}
class AppID_Profile extends projBop_AppObject {
var $belongs_to = array(
'user' => array(
'key' => 'user_id',
),
);
...
}
このような宣言をする必要がありますが、Rails風の命名規則、外部キーはリレーション先のテーブル名_id の規則に則っていれば省略可能なので、上記の例は、
class AppID_User extends projBop_AppObject {
var $has_one = array(
'profile' => null,
);
...
}
class AppID_Profile extends projBop_AppObject {
var $belongs_to = array(
'user' => null,
);
...
}
とすることもできます。この命名規則はここから先のすべての例について適用されます。get
ではさっそく関連オブジェクトの取得を行ってみましょう。まずは、User#id=1のプロフィールオブジェクトを取得する例です。
$user = &$this->backend->getObject('AppID_User', 'id', 1);
$profile = $user->profile->get();
このようにすればOKです。「profile」はリレーションの定義に用いたキー名をそのまま利用しています。そのgetメソッドで関連オブジェクトが取得できます。
では、Profileからユーザを取得する場合は?
$profile = &$this->backend->getObject('AppID_Profile', 'id', 1);
$user = $profile->user->get();
これだけです。非常に簡単ですね。set
取得できたら保存も。
$profile = &$this->backend->getObject('AppID_Profile');
$profile->set('description', 'Ethna-AppObject-Relations');
$user = &$this->backend->getObject('AppID_User', 'id', 1);
$user->profile->set($profile);
これで $profileのuser_id は1として保存されます。また、$profileの保存も自動的に行います。こちらも逆にProfileからUserをセットする場合は?
$profile->user->set($user);
このようにかわるだけです。自動的な保存も同様に行われます。has_many/belongs_to
続いてHasMany/BelongsTo。今回は、「ユーザの出身都道府県」という関係を。まずは定義から。
class AppID_Pref extends projBop_AppObject {
var $has_many = array(
'user' => array(
'key' => 'pref_id',
)
);
...
}
class AppID_User extends projBop_AppObject {
var $belongs_to = array(
'pref' => array(
'key' => 'pref_id',
),
);
...
}
このようになります。先の命名規則による省略を用いると、
class AppID_Pref extends projBop_AppObject {
var $has_many = array(
'user' => null,
);
...
}
class AppID_User extends projBop_AppObject {
var $belongs_to = array(
'pref' => null,
);
...
}
このように省略できます。get/getsAll
ある都道府県にいる人全員を取得してみましょう。
$pref = &$this->backend->getObject('AppID_Pref', 'id', 1);
$users = $pref->user->getAll();
たったこれだけです。Pref#id=1にいるUser#id=2だけを取得する際は、
$user = $pref->user->get(2);
こうなります。逆に、User#id=2の都道府県を取得するには、
$user = &$this->backend->getObject('AppID_User', 'id', 2);
$pref = $user->pref->get();
例によってこれだけです。find
都道府県からみたユーザはたくさんありますが、条件を指定して検索をかけることもfindメソッドで可能です。今回は「性別が男 (gender = M) であるユーザを生年月日若い順(ORDER BY birthday ASC)に10件(LIMIT 10)」という検索条件を加えてみます。
書き方としては、AppManager#getObjectListのfilter等と同じなので取っ付きやすいと思います。
$pref = &$this->backend->getObject('AppID_Pref', 'id', 1);
$filter = array(
'gender' => 'M',
);
$order = array(
'birthday' => OBJECT_SORT_ASC,
);
$limit = 10;
$users = $pref->user->find($filter, $order, $limit);
このようにすることで検索も容易に行えます。set
保存をしてみましょう。新しく作ったユーザをPref#id=2に所属させてみましょう。
$user = &$this->backend->getObject('AppID_User');
$user->set('name', 'BoBpp');
$pref = &$this->backend->getObject('AppID_Pref', 'id', 2);
$user->pref->set($pref);
このようになります。$userは pref->set した時点で、外部キーの設定がされて自動的に保存されます。逆のやり方としては、
$pref->user->set($user);
このようにかわるだけです。こちらも自動的に保存がされます。HasAndBelongsToMany
HABTMにも対応します。「ユーザへのタグ付け」という例を。まずは、定義からですが、
ユーザとタグの関係を表現するテーブルを tag_user テーブルとして、
- id
- tag_id
- user_id
class AppID_User extends projBop_AppObject {
var $has_and_belongs_to_many = array(
'tag' => array(
'relation' => 'tag_user',
'from_key' => 'user_id',
'dest_key' => 'tag_id',
),
);
...
}
class AppID_Tag extends projBop_AppObject {
var $has_and_belongs_to_many = array(
'user' => array(
'relation' => 'tag_user',
'from_key' => 'tag_id',
'dest_key' => 'user_id',
),
);
...
}
このようになります。relationはこの二つのリレーションを表すテーブルです。[id, user_id, tag_id]のみのテーブルとなります。
from_keyはrelationテーブルのカラムの中で、自身の外部キーが入るカラム名を指定してください。
dest_keyはrelationテーブルのカラムの中で、相手の外部キーが入るカラム名を指定してください。
なお、上記三つも省略できます。from_key, dest_keyはBelongsTo,HasOne,HasManyのkeyと同じ命名規則です。
relationは、二つのモデル名を比較して小さい方を前方に置いた、モデル名_モデル名 となります。
よって、上の例も省略記法を用いると、
class AppID_User extends projBop_AppObject {
var $has_and_belongs_to_many = array(
'tag' => null,
);
...
}
class AppID_Tag extends projBop_AppObject {
var $has_and_belongs_to_many = array(
'user' => null,
);
...
}
このように省略できます。get/getsAll
使い方は基本的にHasManyの項目と同じですが、、、Tag#id=1がついたユーザ群を取得するには、
$tag = &$this->backend->getObject('AppID_Tag', 'id', 1);
$users = $tag->user->getAll();
こうなります。Tag#id=1がついているUser#id=2を取得する場合は、
$user = $tag->user->get(2);
こう変わります。set
保存もしてみましょう。User#id=1にTag#id=2をつけてみましょう。
$user = &$this->backend->getObject('AppID_User', 'id', 1);
$tag = &$this->backend->getObject('AppID_Tag', 'id', 2);
$user->tag->set($tag);
このようにします。今回は$user,$tagともに保存済みであることが求められます。ここは要注意です。HasAndBelongsToManyにはfindがありません。いずれ実装されるのかもしれません。。。?
HasManyThrough
不完全ではありますが、一応あります。では、「ユーザは複数のグループに所属する」という関係を扱ってみましょう。まずは定義ですが、
所属する関係をmembershipテーブルで表現し、
- id
- user_id
- group_id
- joined_at
- level
class AppID_Group extends projBop_AppObject {
var $has_many_through = array(
'user' => array(
'relation' => 'membership',
'from_key' => 'group_id',
'dest_key' => 'user_id',
),
);
...
}
class AppID_User extends projBop_AppObject {
var $has_many_through = array(
'group' => array(
'relation' => 'membership',
'from_key' => 'user_id',
'dest_key' => 'group_id',
),
);
...
}
class AppID_Membership extends projBop_AppObject {
var $belongs_to = array(
'group' => array(
'key' => 'group_id',
),
'user' => array(
'key' => 'user_id',
),
);
...
}
こうなります。HasManyThroughの場合はrelationだけ省略不可です。そのため、省略を行うと、、、
class AppID_Group extends projBop_AppObject {
var $has_many_through = array(
'user' => array(
'relation' => 'membership',
),
);
...
}
class AppID_User extends projBop_AppObject {
var $has_many_through = array(
'group' => array(
'relation' => 'membership',
),
);
...
}
class AppID_Membership extends projBop_AppObject {
var $belongs_to = array(
'group' => null,
'user' => null,
);
...
}
このようになります。get/getsAll
get/getsAllもちょっと特殊です。単純にUser#id=1が所属するグループを取得するだけであれば、
$user = &$this->backend->getObject('AppID_User', 'id', 1);
$groups = $user->group->getsAll();
$group = $user->group->get(2); // User#id=1が所属しているGroup#id=2を取得
とHABTMと同じように扱えるのですが、Relationのオブジェクト ここではMembershipも欲しい場合は、
$groups = $user->group->getsAll(TRUE);
foreach ($groups AS $g) {
$membership = $g->RelationObject;
}
// Group#id=3だけの場合は、
$group = $user->group->get(3, TRUE);
$membership = $group->RelationObject;
と、get/getsAllの引数にTrueを追加するとリレーションのリレーションのオブジェクトを取得することができます。RelationObjectというプロパティ名は、現在は固定です。
set
では保存もしてみましょう。User#id=1をGroup#id=4に保存してみましょう。
$user = &$this->backend->getObject('AppID_User', 'id', 1);
$group = &$this->backend->getObject('AppID_Group', 'id', 4);
$user->group->set($group);
これだけです。しかし、Membershipオブジェクトのjoined_at, levelもセットしたいですね。そんなときは、
$user = &$this->backend->getObject('AppID_User', 'id', 1);
$group = &$this->backend->getObject('AppID_Group', 'id', 4);
$membership = &$this->backend->getObject('AppID_Membership');
$membership->set('joined_at', time());
$membership->set('level', 10);
$user->group->set($group, $membership);
このようにsetの第2引数にリレーションオブジェクトを設定すれば同時に保存されます。最後に
今回は、過去最大級のライブラリ?をこんなタイミングでリリースした訳ですが、AppObjectもこれくらいはできるぜ。これでAppObjectでサクサク開発していただければと思います。
バグとかもしございましたら、ご連絡ください。

Trackback
Comment
僕もちょっと足りないだけじゃん、と思っていましたが、まあPEAR依存の排除もどうにかしないといけませぬ(*´~`)