edo1z blog

プログラミングなどに関するブログです

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(&quot;/^[a-z]+$/&quot;,$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(&quot;/^[0-9]+$/&quot;,$page) == 0){
  $this->_renderJson('param &quot;page&quot; 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=&quot;error&quot;></div>
<div id=&quot;uw&quot;></div>
<div id=&quot;pagenation&quot;></div>
<img id=&quot;loading&quot; src=&quot;<?php echo $html->url(&quot;/img/loading.gif&quot;);?>&quot; />


Javascript

$(function(){
 $(document).ready(function(){get_uw();});
 $('#page').live('click',function(){get_uw();});
 $(&quot;#loading&quot;).bind(&quot;ajaxSend&quot;, function(){$(this).show();})
  .bind(&quot;ajaxComplete&quot;, 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: &quot;POST&quot;,
  dataType: &quot;json&quot;,
  data: {'page':page},
  url: &quot;./get_uw_pagenation&quot;,
  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=&quot;wordset&quot; class=&quot;clearfix&quot;><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=&quot;page&quot; type=&quot;button&quot; value=&quot;More?&quot; name=&quot;';
 str += page +  '&quot; />';
 $('#pagenation').append(str);
}