CakePHP1.3.6:トランザクション

個人商店は忙しい。ここのところ、こまごましたことに振り回せれてる。
そういえば、HTML5は素晴らしい!!これも検証しないといけませんね。

CakePHPでは、モデルにトランザクション・コントロールが用意されている。

テストで使っているMySQL(5.1)のInnoDBストレージエンジンでは、デフォルトの設定でrepeadable readの分離レベルが設定されている(こちら)。

昨年のいつだったか、Key-Value Storeの記事を読んでいて、「トランザクション制御を行わないことによる不整合発生のリスクと、データをハンドルする際の速度・簡便さを天秤にかける」という考え方に出会った。昔なら「それは・・・」と思ったに違いないのだが、現在は、そういう考え方もありだなぁ、と思う。実際に機械やネットワーク、OS、RDBMSの信頼性も(一昔前に比べると)格段によくなっているし、今は、InnoDBであろうが、MyISAMだろうがあまり気にしない。

だが、特殊なケースでは、やはり、トランザクションを制御したいときもあるはずである。(たとえば、RDBファイルシステムの両方に同時にデータを保存する場合など)。

前置きが長くなったが、今回は、トランザクションコントロールを実験する。

まず、以下のDDLでeventテーブルを作成する。

CREATE TABLE IF NOT EXISTS `events` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `customer_id` int(11) NOT NULL,
  `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `date` text COLLATE utf8_unicode_ci NOT NULL,
  `time` text COLLATE utf8_unicode_ci NOT NULL,
  `memo` text COLLATE utf8_unicode_ci NOT NULL,
  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;

ここで、以下の画面をindexアクション、その下のようなaddアクションを実装して、実験してみる。

モデル

/app/model/event.phpを作成する。
MyValidator.phpは自作したバリデーション用のクラス。ここでは本質的ではない。

<?php 
require_once '../vendors/MyValidator.class.php';

class Event extends AppModel {
	public $name = 'Event';
	
	public $belongsTo = array(
		"Customer"	=> array(
			'className' => 'Customer',
			'conditions' => '',
			'order' => '',
			'dependent' => false,
			'foreignKey' => 'customer_id'
		)
	);
	
	public $validate = array(
				'title'=>array(
							'rule' => 'notEmpty',
							'message' =>'入力してください'),
				'date'=>array(
							array(
								'rule' => 'notEmpty',
								'message' =>'入力してください'),
							array(
								'rule' => array('checkDate','date'),
								'message' =>'yyyy/mm/ddの形式で入力してください'),
							)

	);

	public function checkDate($data,$name){
		$ret = MyValidator::validate('ja', 'isDate', $data[$name]);
		return $ret;
	}
}
?>

ビュー

/app/views/events/add.ctpだけ以下に示す。

<h2>イベントの登録</h2>
<?php 
echo $form->create(null,array('type'=>'post','action'=>'add'));
?>

<table>

<?php 
echo "<tr><th>件名</th>";
echo "<td>{$form->text('Event.title',array('size'=>'50'))}{$form->error('Event.title')} </td>";
echo "</tr>";
echo "<tr><th>顧客</th>";
echo "<td>{$form->select("Event.customer_id",
		$cus['data'], 		// array('id'=>'Customer.name')
		$cus['selected'],    // selected 
		array('empty'=>'選択してください'))}</td>";
echo "</tr>";
echo "<tr><th>日付</th>";
echo "<td>{$form->text('Event.date',array('size'=>'15'))}{$form->error('Event.Date')}</td>";
echo "</tr>";
echo "<tr><th>時間</th>";
echo "<td>{$form->text('Event.time',array('size'=>'10'))}{$form->error('Event.time')}</td>";
echo "</tr>";
echo "<tr><th>メモ</th>";
echo "<td>{$form->textarea('Event.memo')}</td>";
echo "</tr>";
echo $form->end('登録');
?>
</table>
<a href=".">リストに戻る</a>

コントローラー

events_controller.phpのaddアクションを以下に示す。トランザクションの処理は以下。

	/**
	 * 
	 * データの追加
	 * 
	 */
	function add(){
		$this->set('title_for_layout', "イベントの登録");
		if(!empty($this->data)){
			// サニタイズ
			$this->data["Event"] = MyConverter::getRequestParams($this->data["Event"]);
			// customer_idは必須入力でないので、nullのとき0を入れる。
			if(!$this->data["Event"]['customer_id']) $this->data["Event"]['customer_id']=0;
			// 登録
			$this->Event->begin(); // トランザクションの開始
			if($this->Event->save($this->data)){
				$this->Event->commit();		// コミット
				// 登録できたらリダイレクト
				$this->redirect('.');
			}else{
				$this->Event->rollback();	// ロールバック
				$this->Session->setFlash('データの保存に失敗しました');
			}
		}
		// カテゴリーリストの作成; array('id'=>'name')の作成
		$item = $this->Customer->find('list',array('fields'=>array('id','name')));

		// selectボックス用データの生成
		$ret = array('data'=>$item,'selected'=>null);
		$this->set('cus',$ret);
	}

当然、コミットせずにindex()にリダイレクトすると、データは保存されない。

当たり前のことだが、MyISAMストレージエンジンを使っている場合には、begin()の有無に関わらずデータが保存される。

こんな記事があるので、cacheには注意が必要かもしれない。
$cacheQueriesはmodel.phpに設定されているが、Cake1.3では、デフォルトでfalseになっている。