ラベル CakePHP の投稿を表示しています。 すべての投稿を表示
ラベル CakePHP の投稿を表示しています。 すべての投稿を表示

2008-04-10

こんな model はこうする(ことができるかも?)

こんな model はどうする? の続きです。

Oyatbl - hasMany - Kotbl - belongsTo - Betsutbl

のような関係の kotbls_xxx が 200 個近くあります。
アクセスする kotbls_xxx は、oyatbls のフィールドの値で決まります。kotbls_xxx は、別の masters テーブルの id に対応(xxx = id)した個数あります。

$this->Oyatbl->Kotbl->setSource('kotbls_' . $id);

で、すんなり動きました。ただし、Kotbl モデルの定義で、あとで setSource するからといって Kotbl->useTable = false としてしまうと belongsTo Betsutbl アソシエーションが効きません。setSource では、アソシエーションの再構築が行われないようです。
Kotbl->useTable = 'kotbls_xxxどれか' と定義しておくか、setSource のあとで bindModel/unbindModel する必要がありました。

また、上の記述とアソシエーション定義を見て気付いたのですが

var $hasMany = array(
'assocalias' = array(
'className' => 'Kotbl',
...

この場合は、className Kotbl モデルのインスタンス(オブジェクトだっけ?)が assocalias という名前で生成されるということらしいです。
なので、こんな荒業も

$ids = $this->Master->find('list', array('id'));
foreach ($ids as $id) {
$assocKey = 'Kotbl' . $id;
$tblName = 'kotbls_' . $id;
$this->Oyatbl->bindModel(array(
'hasMany' => array(
$assocKey => array(
'className' => 'Kotbl',
'foreignKey' => 'fkey',
'dependent' => false
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
)
, false
)
);
$this->Oyatbl->{$assocKey}->setSource($tblName);
}

デバッガで覗いた限りでは、model オブジェクトの中身はそれらしくなっていました。動作の確認は、また明日。しかし、かなりヘビー。大量の SQL 発行とかなりメモリも消費します。実用になるのか?

delete をトランザクションで行いたいのですが、なんか、苦労の方向性が間違っている気がする。DB 設計を見直してリレーションを設定して delete cascade で良いんじゃないか? ただ、既存 DB なので影響範囲にすべて手を入れるとなると...

2008-04-09

アソシエーションで集約関数を使いたい

CakePHP 1.2(6598)。 PostgreSQL 8.1

はまった。親テーブルに対して、子テーブルを集約関数(COUNT,SUM など)でサマった結果を結合したい場合です。

例えば、 User hasMany Post という関係について User hasOne (user_id 毎のPost 数) と、いうアソシエーションを設定できないか。理想は、

    Users
LEFT JOIN
(SELECT user_id, COUNT(id) AS cnt FROM Posts GROUP BY user_id) postcounts
ON
Users.id = postcounts.user_id


こんな問合せが組立てられること。

conditions に GROUP BY を記述する方法は、使えませんでした。GROUP BY が結合条件(ON 節)に入ってしまって、 syntax error です。

とりあえず、2点考えてみましたが、何れも搦め手ですっきりしません。

1. データベースビューを使う


PostgreSQL なのでデータベースビューを作成することができます。

CREATE OR REPLACE VIEW postcounts AS
SELECT user_id, COUNT(id) AS cnt
FROM Posts
GROUP BY user_id

CakePHP でも、テーブルと同様に扱えるようです。データベースビュー (postcounts) に対する model (Postcount) を bake することができました。あとは、通常の model 同様、hasOne に設定します。
長所
  • hasOne アソシエーションなので model->recursive = 0 での問合せに含まれます。検索条件に使えるので PaginatorHelper を使いやすいです。
短所
  • 集約する条件を動的に指定できない。「ある期間内の投稿数」のような条件でサマることは、よくあるんじゃないかな。


2. hasMany の結果をカウントする



hasOne アソシエーションではないのですが、User hasMany Post が設定されているので、model->recursive = 1 で問合せて count(kekka['Post']) で件数は得ることができます。大量の問合せが発行されることを危惧していたのですが、WHERE 節に IN 句が使用された1文が発行されていました。

長所
  • hasMany の conditions に指定することで集約条件を動的に設定できる。

短所
  • 別の文なので、検索条件に含めることはできない。
  • 親テーブルの件数が多い場合には、IN 句が大量となり遅くなる(と、思う)。

CakePHP 的には、hasMany をカウントの方が素直でしょうか。勝手に思っていることですが、model->query を使うと、負けてしまった気分になると思います(^_^;

2008-04-07

こんな model はどうする?

CakePHP 1.2 でのお話。PostgreSQL 8.1。

同一構造で関連ID毎に185個のテーブルがある。2つのマスタテーブルに対して belongsTo でマスタテーブルからは、 hasMany。残念(?)ながら、パーティショニングされてはいないし、するには移行手順が必要。

さぁ、どうする? 185個モデル作るべきなのか? まさかね。

全てのテーブルを同時に参照する必要はなさそうなので参照するテーブルを動的に切り替えるロジックは作れそう。

model->setSource と model->bindModel/unbindModel で行けるのかなぁ? 続きは、明日。

2008-04-03

CakePHP で Dojo toolkit

CakePHP 1.2 で Dojo Toolkit を使ってみた。


全てのページで必要ではないので、layout の scripts_for_cake 変数を使います。

CakePHP Users in Japan フォーラム scripts_for_layoutとは?


試しに Dijit のポップアップカレンダー付き日付入力テキストボックス(dijit.form.DateTextBox)を使ってみます。


Dojo 配置


Dojo を app/webroot/js 以下に配置します。今回は、dojo というディレクトリを掘りました。


app
└─webroot
└─js
└─dojo
├─dijit
├─dojo
├─dojox
└─util


dojo.js と CSS 読み込み



テンプレートに以下を追加します。全てのページで Dojo を必要とはしていないので、 layout ではなくテンプレートに記述して、scripts_for_cake メカニズムを使います。



<?php
$html->css('../js/dojo/dijit/themes/tundra/tundra', 'import', array(), false);
$html->css('../js/dojo/dojo/resources/dojo', 'import', array(), false);
$this->addScript('<script type="text/javascript" src="/絶対URL/js/dojo/dojo/dojo.js" djConfig="isDebug: true, parseOnLoad: true"></script>');
$javascript->codeBlock(' dojo.require("dijit.form.DateTextBox");
dojo.require("dojo.date.locale");
dojo.require("dojo.parser"); // scan page for widgets and instantiate them', array('inline' => false));
?>


Javascript ヘルパの link では、大事な大事な djConfig が設定できなかったので addScript を使いました。弊害として、src 属性に絶対 URL を書かなければいけなくて、なんともかっこ悪い。



フォームにインプット要素を記述



他の form ヘルパの要素と混在可能です。



<?php echo $form->label('created.min', '登録日'); ?>
<input name="data[Moderu][created][min]" type="text" value="<?php echo $this->data['Moderu']['created']['min'] ?>" id="UsrpramCreatedMin"
dojoType="dijit.form.DateTextBox"
constraints="{min:'2003-01-01',max:'2013-12-31',datePattern:'yyyy/MM/dd'}"
promptMessage="登録日の検索範囲"
trim="true" />


注意点



これで、そこそこ表示されますが、落し穴が2点ほど




  1. layout の body タグに dijit テーマの class 属性が必要。

    今回は、tundra を使っているので <body class="tundra"> としないとかっこよくならない。

  2. CSS の基本セレクタが衝突。 cake.generic.css を一部変更して使っていると妙な表示(今回の場合、input ボックスの高さが異常にでかかった)になりました。

    form div セレクタを削除して対処しました。



djConfig や constraints などをうまく扱うためには、やはりヘルパが欲しいところです。

2008-03-13

CakePHP 1.2 は、webservices に対応しない。

CakePHP アップデートで携帯ページ NG の件の「次回」は、割と早い時期にやってきそうです。

ソースコードリポジトリの 1.2.x.x ブランチのChangeset [6453] で webservices 関係がばっさり削除されてます。1.2 リリースでは、確実に別の実装を行わないとだなぁ。

これは、別件調べ事のついででした。それは、PostgreSQL を使っている場合に UPDATE 文に alias が入って失敗するというもの。

http://cakephp.jp/modules/newbb/viewtopic.php?post_id=1814&topic_id=934&forum=6

こちらでも議論がされていますが、1.2.0.6311-beta のはまりねたらしいです。部分的に差し替えようかと思ったのですが、変更範囲がかなり広い。幸か不幸か、携帯ページを使っていなかったので cake ディレクトリを HEAD Revision に全とっかえして、UPDATE 文は、通るようになりました。まだ、時間的に余裕があるから良いけど、今後も、こんなことはありそうだから、ユニットテストはきちんと書いておこう。

2008-03-12

CakePHP アップデートで携帯ページ NG

CakePHP で作成したサイトの携帯ページを CakePHP 携帯用ビューを表示する(Shin x blog さん) などを参考に実装していました。当初は、1.2.0.5875-pre-beta を使ってうまく行っていたのですが 1.2.0.6311-beta にアップデートしたところ、文字化けに見舞われました。念のため Subversion にブランチして作業していたのですぐに戻せるのですが、悔しいので少し調べてみました。

状況を落ち着いてみてみると、どうやら View は、webservice のものが選択されているが、Layouts が通常のものの模様。render まわりと当たりをつけて cake/libs/view/view.php を diff してみたら、かなり大幅に変ってる。他は、ほぼ問題ないので _getViewFileName と _getLayoutFileName のみに着目すると、やっぱり、webservices 判定が _getViewFileName にはあるが、_getLayoutFileName からはなくなってました。

@@ -821,6 +821,9 @@
}
$subDir = null;

+ if (!is_null($this->webservices)) {
+ $subDir = strtolower($this->webservices) . DS;
+ }
if (!is_null($this->layoutPath)) {
$subDir = $this->layoutPath . DS;
}


こんな修正で動くようにはなります。しかし、コアに変更入れるのは、さて、どうしたものか...



cake/libs/router.php には 「webservice は、非推奨で将来的にはサポートしなくなるよ。Router::parseExtensions() を使ってね。」という記述がありますね。1.2 系で作業始めたときも情報の少なさに「しくじったか?」とは、思ったのですが...



次回からがんばります。

2008-01-08

CakePHP で携帯ブラウザ判定

CakePHP 1.2 でのおはなし。

PEAR::Net_UserAgent_Mobile を使おうと思っていた。

参考:

CakePHP プログラマーズ リファレンスガイド を読んでたら、 RequestHandler に isMobile なるメソッドを発見。ソースを見てみたら 'DoCoMo' なども判定できるようなので簡易的には、行けそう。

コントローラでコンポーネントの宣言をして

    var $components = array('RequestHandler');




以下の判定で携帯用のURLにリダイレクトをしたりする。




            if ((!isset($this->params["webservices"]) || $this->params["webservices"] != "Mobile")) {
if ($this->RequestHandler->isMobile()) {
$this->redirect('携帯のURL', null, true);
}
}




Vodafone 時代の携帯は、判定できた。あとは、新しめの Softbank と au。