INFRA

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);
}