がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

data_clumpの使い方(ざっくり版)

ほぼ私信のようなもんですがw
細かくはまたマニュアル( https://github.com/gallu/MagicWeaponManual/blob/master/table_of_contents.md )のほうに書きますが、一端、メモ程度に。

本質的なところ

data_clumpは「データの塊」です。
例えば「1つのmail form」は一塊のデータだと思われますし、DBの1テーブルなんてのも、一塊のデータだと思われます。
そんな「一塊」ごとに1クラスをアサインしているのがdata_clumpです。

下ごしらえ

とりあえず「data_clump継承クラス」を作成してください。
場所は、MagicWeaponの流儀をそのまま持ち込むのであれば、libディレクトリにclumpってディレクトリを切って、そこに置くとよいです。
また必須ではないのですが、clumpディレクトリの中にbaseってディレクトリを切っておくと、「Generation Gapパターン」的な意味でよりよいです。


例えば。
http://furu.mwtest.gjmj.net/
で動きを見ていただけて、ソースコード
https://github.com/gallu/MagicWeaponTest
にあるのですが。
「1つのform」を構成するクラスは、
https://github.com/gallu/MagicWeaponTest/blob/master/lib/clump/base/form_test_clump_base.inc
https://github.com/gallu/MagicWeaponTest/blob/master/lib/clump/form_test_clump.inc
の2つのクラスに分解すると、色々とはかどります。


「clump/base/*_clump_base.inc」は、大まかには「このデータの塊は具体的にどんな要素をもっているのか」が記述してあります。
「clump/*_clump_base.inc」は、data_clumpの本体になります。
formだと難しいのですが、「DBのテーブル」単位でclumpを作る場合は、MagicWeaponをインストールしたディレクトリのtool/soak_up_information.php( https://github.com/gallu/MagicWeapon/blob/master/tools/soak_up_information.php )ってのを叩くと、自動でコードを生成してくれます。
もうちょっと正確に書くと「baseは常に上書きで生成(するからこのファイルは追記とかしないようにしたほうが無難)」「clumpは"なければ作る"」って動きをします。


以下、上述のファイルを作ってる前提で話をすすめます。

formからのデータの取得とかvalidateとか出力とかその辺

ものすごく大まかには、以下のようなコードで大体処理をします。
https://github.com/gallu/MagicWeaponTest/blob/master/lib/form_confirm.inc
(ちょっとコメント変えてます)

  // data_clumpインスタンスを取得
  $form_test_c = $this->get_clump('form_test_clump');

  // 「cgi requestから」データを取得
  $form_test_c->set_from_cgi($this->get_cgi_request());

  // validate(定型のみ)
  if (false === $form_test_c->is_valid($this->get_conv())) {
    $this->forward('form_input');
    return ;
  }

  // セッションに保存
  $_SESSION['form_test'] = $form_test_c->get_all_data_to_hash();

  // 表示
  $this->get_conv()->set('form', $form_test_c->get_all_view_values());

インスタンスは「newで作る」でもよいのですが、MagicWeaponのMVCを使っている場合は「model(ほかのフレームワークのcontroller/action)から取得可能」なので、そこで取得してます。
base_model#get_clump()で取得すると「newした後でDBハンドルぶち込んどいてくれる」程度に便利です…が、上述のような「formからの取得だけ」なら、あんまり意味はないです(笑
まぁ「他とそろえた方が見やすい」程度かなぁ。


data_clumpは「一塊のデータ」なので「cgi requestから、一塊を一式取得しといて」は、1メソッド set_from_cgi() で片付けます。
同様に、validateも「どう? valid?」なので、1メソッド。
ちなみにvalidateについては、「定型で片付く」範疇までなら、protected function set_element()ん中のpush_validateで定数とか設定しておけば、後は自動。
「もうちょっとややこしいvalidateが絡む」場合は、is_valid_insert()またはis_valid_update()を上書きします。先にparent::で「定型処理のvalidate」呼んでもらって、残りの「ややこい」のは自力で適宜追記実装してください。


validaだった場合。
data_clumpは、内部的に「data」と「view_values」を微妙に使い分けているので、使い分けながら「セッションに情報を保存しつつ」「表示しつつ」します。
dataは「内部データ構造」。なので、DBとのやりとりとか、いわゆる「生データ」がここになります。
一方でview_valuesは「表示用のデータ」。
dataと等しいケースもあるけど「表示用にちょっと色々小細工してみたい」なんてケースもあるので、その場合はview_valuesメソッドを上書きして、parent::で情報取得した後に「小細工いれてreturn」とかやると、色々と便利です。


で、データを最終的に受けとるのはこっち。
https://github.com/gallu/MagicWeaponTest/blob/master/lib/form_fin.inc

  // clumpに一度通してからviewへ
  $form_test_c = $this->get_clump('form_test_clump');
  $form_test_c->set_all_data_from_hash($_SESSION['form_test']);

  // 表示
  $this->get_conv()->set('form', $form_test_c->get_all_view_values());

表示するので、言い方を変えると「view_valuesの処理を通したい」ので。
set_all_data_from_hash()で「生データをぶち込んで」から、get_all_view_values()でviewに変数を一式セット。
これを定型でやっておくと「なんか表示上の変更があっても、get_all_view_values()とテンプレート修正すればOK」なので、DRYに近い感じでよいのです。


上述コードは「ほかはなんもやってない」のですが、実際には、mailを作って送ったり、とかするんだと思う。
get_all_view_values()があるからあんまりこの状態だと使わない気がするんだけど、例えば上述で「text_dataのデータ単体が欲しい」場合は、「get_value('text_data')」で取得可能。

formからのデータの取得をDBにぶち込む系

大体いっしょ。
サンプルコードがないのでざっくり概念だけ書くと、finのタイミングで

  // clumpに一度通してからviewへ
  $form_test_c = $this->get_clump('form_test_clump');
  $form_test_c->set_all_data_from_hash($_SESSION['form_test']);

  // DBにinsert
  $r = $form_test_c = $this->insert();
  if (false === $r) {
    // insertできなかったお orz
  }

  // 表示
  $this->get_conv()->set('form', $form_test_c->get_all_view_values());

これくらい。
set()ってメソッドもあって、いわゆるupsertなんだけど、最近あんまり使わないようにしてるので多分そのうち非推奨になる予定w
ちなみにinsertの場合、「set_insert_date_name()」ってのが設定されていると、設定されたカラム名に「現在日付」が入る感じ。


で…DBにinsertする場合。
set_from_cgi(正確にはset_from_cgi_insert)でデータを取得するときに、例えば「idはauto incrementなのに、パラメタインジェクションで外部から指定されちゃってショック!!」ってのが、起きないとも限らない。
なので、そーゆー時はset_from_cgi_insert()を上書きしてくださいませ。
set_from_cgi_insertは内部的に「set_from_cgi_detailをcallしている」だけで、set_from_cgi_detailは

 * @param cgi_request $req cgi_requestクラスのインスタンス
 * @param vector $target 対象とするcgi name attribute値
 * @param boolean $empty_overwrite_flg 空文字の上書きフラグ trueにすると空なら空文字を上書きする

なので。
第二引数の「$target」から、例えば「自動で設定したいPKのカラム名」を抜いたりすればOK。
$this->get_all_names() で「全パラメタ名」が配列で取得できるので、そこから「抜きたいのを削除する」ってロジックを書くと楽。
おいちゃんは
・array_flipで値とkeyを反転
・unsetでkeyを指定して削除したいカラム名を削除
・array_flipで戻す
ってやり方をよくやるかな。
これなら「後でカラム名が増えても」気にせずやっていけるからw

update系

処理としては似てるんだけど。
やり方的には
・keyに対応する情報をDBから取得
cgi requestで「修正すべき値」が投げられてくる
・DBをupdate
って手順になると思う。


雑にソースコードを書くと

  // clumpインスタンス作成
  $hoge_c = $this->get_clump('hoge_clump');

  // keyを設定
  // XXX IDが空かどうかのチェックは省略
  $hoge_c->set_value('hoge_id', $this->get_cgi_request()->find('hoge_id'));

  // DBから取得
  $r = $hoge_c->get();
  if (false === $r) {
    // データないってよ!!
    適宜エラー処理各種
    return ;
  }

  // 表示
  $this->get_conv()->set('hoge', $hoge_c->get_all_view_values());

で、まず表示。
修正内容取得&(確認画面抜きにして)修正なら

  // clumpインスタンス作成
  $hoge_c = $this->get_clump('hoge_clump');

  // 「cgi requestから」データを取得
  $hoge_c->set_from_cgi_update($this->get_cgi_request());

  // validate(定型のみ)
  if (false === $hoge_c->is_valid_update($this->get_conv())) {
    適宜エラー処理各種
    return ;
  }

  // DBの内容編集
  $hoge_c->update();

  // 表示
  $this->get_conv()->set('hoge', $hoge_c->get_all_view_values());

こんな感じ。
set_insert_date_name()とほぼ一緒な感じで、set_update_date_name()ってメソッドがあるので。
「修正日」とかいうカラムがある系なら、ご利用くださいませ、的な。


あと「set_from_cgi_updateでデータを取得するときに、変更させたくないパラメタなのにパラメタインジェクションで外部から指定されちゃってショック!!」ってのが、起きないとも限らないので、的な、insertと同じお話。
set_from_cgi_updateは内部的に「set_from_cgi_detailをcallしている」だけなのと、 $this->get_all_no_key_names() で「pk以外のカラム名一式」が取得できるので、insertん時と同じように「適宜、抜くべきカラム名は抜いて」あげてくださいませ。

「PK以外のデータを指定して」情報を引っ張ってきたい場合

例えばユーザデータなんかで
・IDはint
・emailがユニーク
・emailからユーザを引っ張ってきたい
なんてケース。

  // clumpインスタンス作成
  $users_c = $this->get_clump('users_clump');

  // keyを設定
  // XXX IDが空かどうかのチェックは省略
  $users_c->set_value('email', $this->get_cgi_request()->find('email'));

  // DBから取得
  $r = $hoge_c->get_nopk();
  if (false === $r) {
    // データないってよ!!
    適宜エラー処理各種
    return ;
  }

ようは、get()がget_nopk()に変わるだけ。
ただこれ「複数引っかかる場合、なにが引っかかるかは保障されない」ので、注意してね。

「一覧」とかを処理する系

MagicWeaponの基本の一つは「SQLは書いて」なのでw
その辺を前提に、一覧系を、やっぱり雑なコードで簡単に解説。

  // なんか「特定のstatus」を持ってるユーザの一覧とか検索
  $mw_sql = new mw_sql();
  $mw_sql->set_sql('SELECT * FROM users WHERE status=:status;'); // プリペアドステートメントを設定
  $mw_sql->bind(':status', $this->get_cgi_request()->find('status')); // 値をbind
  $res = $this->get_db()->query($mw_sql); // SQLの発行
  $res->set_fetch_type_hash(); // fetchのタイプをhash(カラム名)に変更

  // data_clumpのインスタンスを再利用してちょっとだけメモリ節約用:昔は結構重要だった。今はどうかなぁ?
  $users_c = null;

  // データがなくなるまでぶん回す
  $users_list = [];
  while($res->fetch()) {
    $users_c = $this->get_clump('users_clump', $users_c); // 第二引数がnullならnew、nullでなければiniti叩いてインスタンス初期化して再利用

    // データを「db_dataインスタンスから」取得
    $users_c->set_from_dbdata($res);

    // view用データを蓄積
    $users_list[] = $users_c->get_all_view_values();
  }

  // viewに設定
  $this->get_conv()->set('users_list', $users_list);


大体こんな感じ。

その他雑多で「まぁまぁ使う」子たち

del()
データの削除。単純にdeleteなんだけど、
「もし、テーブル名 + '_delete'っていう名前のテーブルが存在する」場合、そちらへのinsertを同時にやってくれる、ってあたりがちょっとだけ小細工。
テーブル名_deleteのテーブルには、テーブル名のカラム+「delete_date」ってカラム、が必須。


set_value_nowdate()
set_valueとほぼ等価なんだけど、値は「日付が自動で入る」ので、稀に便利。


set_value_token()
set_valueとほぼ等価なんだけど、値は「tokenが自動で入る」ので、稀に便利。
ちなみにtokenは、tokenizerクラス( https://github.com/gallu/MagicWeapon/blob/master/tokenizer.inc )の値。


set_value_token_with_ip()
set_value_token()とほぼ一緒なんだけど「IPアドレス付き」になるので、複数サーバでも無問題。


update_calculation()
「1つのカラム」の数値を加減算できるメソッド。


set_insert_id()
auto_increment時に、insertの後でこのメソッドをcallすると「IDがclumpの中に入ってくる」ので、後の取り回しが楽、かも。


あとは、data_clumpは「実はデータをmemcachedに入れられる」とか「実はデータをAPCん中に入れられる」とか、細かいギミックがいくつか。


…うん思った以上に長くなった(苦笑
まぁなんだかんだ、ある程度「MagicWeaponに特徴的なクラス」なので、細かくは色々な機能がありますw
でもまぁベースにあるのは「データを一塊で扱う」以上終了、なので。


ソースコードは、またどっかのタイミングで整理しないとねぇ(苦笑


PS
この文章は本気で「見直しをしていない」ので、突っ込みは大歓迎w
多分、定期的に修正いれますw