2012年1月17日火曜日

cakePHP1.3でデータベース操作の研究(MySQL)

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となります。



各テーブルの項目

各テーブルのフィールドは下記を想定しています。テスト機能つけたり色々一応想定して項目をつけました。まあ全部つくるか分かりませんがモチベーションが続くまで研究してみます。

usersid,name,image,discription
wordsid,word
imisid,imi
uwsid,user_id,word_id,count,seitouritsu,seigo
iwsid,imi_id,word_id,goodcnt,badcnt
iuwsid,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);
}

0 件のコメント:

コメントを投稿