cakePHPのデータベース操作を研究します。データベースはMySQLです。cakePHPのバージョンは1.3を使ってます。cakePHPはバージョン毎に結構変化が色々あるようなので、cakePHP2.0との違いは早く調べておこうと思います。どんなデータをつくるかというと、分かり易いのがいいので英単語帳をイメージしてテーブルを作ってみます。
テーブル構成
テーブル構成は下記を想定してます。言葉遣いが間違っている可能性はありますが、iuwは、uwとiwを参照します。uwはuserとwordを参照します。cakePHPのJOINの構文を見ると、wordはuwを沢山持っていると言います。word hasmany uwとなります。逆にiuwはuwに属するといいます。iuw belongsTo uwとなります。
各テーブルの項目
各テーブルのフィールドは下記を想定しています。テスト機能つけたり色々一応想定して項目をつけました。まあ全部つくるか分かりませんがモチベーションが続くまで研究してみます。
users | id,name,image,discription |
words | id,word |
imis | id,imi |
uws | id,user_id,word_id,count,seitouritsu,seigo |
iws | id,imi_id,word_id,goodcnt,badcnt |
iuws | id,uw_id,iw_id |
トランザクション
トランザクションを使うための準備をします。app/models内にtransaction.phpを作ります。
Transactionモデル
<?php class Transaction extends AppModel { var $useTable = false; function begin () { return $this->getDataSource()->begin($this); } function commit () { return $this->getDataSource()->commit($this); } function rollback () { return $this->getDataSource()->rollback($this); } } ?>
トランザクションの動作確認をします。
コントローラー
//$usersにTransactionを入れるのを忘れないようにする public $uses = array('User','Word','Imi','Uw','Iw','Iuw','Transaction'); //トランザクションの実験(動作確認) public function tora(){ $word = 'hogehgoe'; //トランザクション開始 $this->Transaction->begin(); //wordの登録 $word_data = array('Word' => array('word' => $word)); if(!($this->Word->save($word_data))){ $this->_renderJson(0); return; } //$this->Transaction->commit(); $this->Transaction->rollback(); $this->_renderJson(1); }
動きました。上記はコミットをコメントアウトしてロールバックしているので、データは登録されませんでした。コミットすると登録されました。
データの検索
最もシンプルな検索方法
$word_data = $this->Word->findByWord($word);
二番目にシンプルな検索方法
$conditions = array( 'Iw.imi_id' => $imi_id, 'Iw.word_id' => $word_id); $iw_data = $this->Iw->find($conditions);
三番目にシンプルな検索方法
$params = array('conditions' => array( 'Uw.user_id' => $user_data->id, 'Uw.word_id' => $word_id)); $uw_data = $this->Uw->find('first',$params);
高機能な検索方法
//IwにIuwを持たせる(バインド設定) $this->Iw->bindModel(array('hasMany'=>array('Iuw'))); $params = array( 'conditions' => array('Iw.word_id' => $word_id), 'recursive' => 3, 'limit' => 20, 'order' => 'goodcnt DESC'); //Iwデータ及びIuwデータの取得 $data = $this->Iw->find('all',$params);
recursiveは、検索するときの結合の深さになります。上記のケースですと、bindModelを使って、IwにIuwを持たせています。また、下記に記載しますが、モデル間のbelongsToの関係のみ、各モデル側で事前設定しています。よって、この検索結果は、下記のようになります。
「該当する全てのIwには、(1)Iwが持っているIuwと、(2)Iuwが属しているIwとUwと、(3)Iwが属しているWordとImi、Uwが属しているUserとWordが付いてきます。」
超分かりにくいですが、(1)->(2)->(3)の具合で階層が深くなっている様です。超分かりにくいです。
ちなみに、belongsToについてのみ各モデル側で事前に下記のように設定しています。
<?php class Iuw extends AppModel{ var $name = 'Iuw'; var $belongsTo = array( 'Iw' => array( 'className' => 'Iw', 'foreignKey' => 'iw_id', 'limit' => '5', ), 'Uw' => array( 'className' => 'Uw', 'foreignKey' => 'uw_id', 'limit' => '5', ) ); } ?>
上記はIuwモデルクラスです。iuw.phpの中身になります。iuwはuwとiwに属するので2つ$belongsToに設定しています。
データの削除
//wordを削除 if(!($this->Word->delete($word_id))){ return 'error delete word'; }
データの更新
パターン1
//ユーザーのword_countに登録 $this->User->id = $user_id; $this->User->saveField('word_count',$word_count);
パターン2
//uwを更新 $uw_data = array('Uw' => array( 'id' => $uw_id, 'count' => $uw_count, 'right_count' => $uw_right, 'seitouritsu' => $uw_seitouritsu, 'seigo' => $uw_seigo)); if(!($this->Uw->save($uw_data))){ return 'error save Uw'; }
プライマリーキーが含まれていればsaveでも更新になる。
データの登録
$iw_data = array('Iw' => array( 'imi_id' => $imi_id, 'word_id' => $word_id)); if(!($this->Iw->save($iw_data))){ return 'error save Iw'; }
order =>rand()
//テストの問題を出す(データをランダムに一つ返すだけ) public function test_get_word(){ $user_data = $this->_first_action(); //バインド設定(UwにIuwを持たせる) $this->Uw->bindModel(array('hasMany'=>array('Iuw'))); //uwを取得する $params = array( 'conditions' => array('user_id' => $user_data->id), 'order' => 'rand()', 'recursive' => 3); $uw_data = $this->Uw->find('first',$params); $this->_renderJson($uw_data); }
ランキング
//ランキングを算出 $params = array('conditions' => array( 'User.word_count >' => $word_count)); $rank = $this->User->find('count',$params); $rank++;
トランザクションを使っている様
//単語登録(単語と意味を直接登録するパターン) public function submit_word(){ $user_data = $this->_first_action(); //フォーム内容word,imiの有無チェック if(empty($this->params['form']['word']) || empty($this->params['form']['imi'])){ $this->_renderJson('error param is empty'); return; } $word = $this->params['form']['word']; //imiのタグを除去(無効化) $imi = htmlspecialchars($this->params['form']['imi']); //wordの内容チェック(半角小文字の英単語1文字か?) if(preg_match("/^[a-z]+$/",$word) == 0){ $this->_renderJson('error word is wrong'); return; } //トランザクション開始 $this->Transaction->begin(); //ワード登録処理 $result = $this->_submit_w($user_data->id,$word,$imi); //登録処理が成功した場合 if($result == true){ //トランザクションをコミット $this->Transaction->commit(); //ユーザのワード数を登録 $this->_put_word_count($user_data->id); //登録処理が失敗した場合 }else{ //トランザクション ロールバック $this->Transaction->rollback(); } $this->_renderJson($result); }
AjaxでPagenation
コントローラー
//Uwを取得(Ajaxでページネーションする想定) public function get_uw_pagenation(){ $limit = 20; //1pageに何データ表示するか? $user_data = $this->_first_action(); //フォーム内容の有無チェック if(empty($this->params['form']['page'])){ $this->_renderJson('error page is empty'); return; } $page = $this->params['form']['page']; //pageの内容チェック if(preg_match("/^[0-9]+$/",$page) == 0){ $this->_renderJson('param "page" is wrong'); return; } $offset = $limit * $page; //バインド設定(UwにIuwを持たせる) $this->Uw->bindModel(array('hasMany'=>array('Iuw'))); //Uwの取得 $params = array( 'conditions'=> array('user_id' => $user_data->id), 'order' => 'seitouritsu DESC', 'limit' => $limit, 'offset' => $offset, 'recursive' => 3); $uw_data = $this->Uw->find('all',$params); $this->_renderJson($uw_data); }
View
<?php echo $html->script('index',array());?> <div id="error"></div> <div id="uw"></div> <div id="pagenation"></div> <img id="loading" src="<?php echo $html->url("/img/loading.gif");?>" />
Javascript
$(function(){ $(document).ready(function(){get_uw();}); $('#page').live('click',function(){get_uw();}); $("#loading").bind("ajaxSend", function(){$(this).show();}) .bind("ajaxComplete", function(){$(this).hide();}); }); function get_uw(){ $('#error').empty(); var page = $('#page').attr('name'); if(!page) page = '1'; if(!(page.match(/^[0-9]+$/))){ $('#error').append('page is wrong.'); return; } $.ajax({ type: "POST", dataType: "json", data: {'page':page}, url: "./get_uw_pagenation", success: function(data){ if(data != false){ write_uw(data); write_page(page); } } }); } function write_uw(data){ $('#pagenation').empty(); var str = ''; for(var i=0;i<data.length;i++){ str += '<div id="wordset" class="clearfix"><ul><li>'; str += data[i]['Word']['word'] + '<li>'; for(var j=0;j<data[i]['Iuw'].length;j++){ str += data[i]['Iuw'][j]['Iw']['Imi']['imi']; if(!(j == data[i]['Iuw'].length - 1)){ str += ' | '; } } str += '<li>'; str += data[i]['Uw']['seitouritsu']; str += '</ul></div>'; } $('#uw').append(str); } function write_page(page){ page++; var str = '<input id="page" type="button" value="More?" name="'; str += page + '" />'; $('#pagenation').append(str); }