
変更履歴を自動で取れる仕組みが欲しかったので
ビヘイビアで実装してみました。
初めてビヘイビア作ったのでハラハラしました。
変更履歴のイメージ
更新した時に
いつ誰がどのテーブルを変更してたのかが分かるように
更新前と更新後のデータを残しておく
といった感じで作成しました。
ログを取るためようにchange_logsテーブルを作成
まず、更新履歴を残すためのテーブルを作ります。
change_logsテーブル
※SoftDeleteBehaviorを使うことを前提としてます。
-- -- テーブルの構造 `change_logs` -- CREATE TABLE IF NOT EXISTS `change_logs` ( `id` int(11) NOT NULL AUTO_INCREMENT, `table_name` varchar(100) DEFAULT NULL, `table_id` int(11) DEFAULT NULL, `mode` varchar(100) DEFAULT NULL, `user_id` int(11) DEFAULT NULL, `old_value` text, `new_value` text, `created` datetime DEFAULT NULL, `modified` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=5376 ;
table_nameは更新されたテーブル名
table_idは更新したテーブルid
modeには新規か更新か削除の種類
user_idは変更した人
※ログインしている人を対象としています。必要ない場合はこのカラムはいらないかも
old_valueは更新前
new_valueは更新後のデータです。
ChageLogBehavior.phpを作成
ログインしていう人を対象にしています。
必要ない場合にはuser_idの部分は削除してもいいと思います。
Model/Behavior/ChageLogBehavior.php
※SoftDeleteBehaviorを使うことを前提としてます。
class ChangeLogBehavior extends ModelBehavior {
public $name = 'ChangeLog';
function beforeSave(Model $model){
//ログインユーザー
$user_id = CakeSession::read("Auth.User.id");
$id = $model->id; //IDがセットされているか
$new_value = $model->data; //保存用のデータ
$old_value = array();
$mode = "insert";
//セーブ前にidがあれば更新
if($id){
$mode = "updata";
$model->recursive = -1;
$old_value = $model->find('first', array(
//Model.id => $idの形でDBから更新前のデータを取ってくる
'conditions' => array($model->alias . "." . $model->primaryKey => $id
)));
//array_diff_assoc関数を使って配列を比較
$diff_array = array_diff_assoc(@$old_value[$model->alias], @$new_value[$model->alias]);
$new_diff_array = array_diff_assoc(@$new_value[$model->alias], @$old_value[$model->alias]);
//ceatedとmodifiedは省く
unset($diff_array['created'], $diff_array['modified'], $diff_array['deleted'], $diff_array['deleted_date']);
unset($new_diff_array['created'], $new_diff_array['modified'], $new_diff_array['deleted'], $new_diff_array['deleted_date']);
//array_keys関数を使って更新されたカラム(キー)を取得
//新しく追加する項目の方を基準とする
$diff_key = array_keys($diff_array);
//更新するカラムがないものは削除
foreach($diff_key as $key => $value){
if(!isset($new_diff_array[$value])){
unset($diff_array[$value]);
}
}
if(!(empty($diff_array) && empty($new_diff_array))){
$change['table_name'] = $model->table;
$change['table_id'] = $id;
$change['mode'] = $mode;
$change['user_id'] = $user_id;
$change['old_value'] = serialize($diff_array);
$change['new_value'] = serialize($new_diff_array);
$result = $this->saveLog($change);
return $result;
}else{
//ログに保存しないのでtrue
return true;
}
}else{
unset($new_value[$model->alias]['created'], $new_value[$model->alias]['modified'], $new_value[$model->alias]['deleted'], $new_value[$model->alias]['deleted_date']);
$change['table_name'] = $model->table;
$change['table_id'] = "";
$change['mode'] = $mode;
$change['user_id'] = $user_id;
$change['old_value'] = '新規追加';
$change['new_value'] = serialize($new_value);
$result = $this->saveLog($change);
return $result;
}
}
function afterSave(Model $model) {
$id = $model->id;
$result = $this->saveId($id);
return $result;
}
function beforeDelete(Model $model) {
//ログインユーザー
$user_id = CakeSession::read("Auth.User.id");
$id = $model->id;//IDがセットされているか
$mode = "delete";
$model->recursive = -1;
$old_value = $model->find('first', array(
//Model.id => $idの形でDBから更新前のデータを取ってくる
'conditions' => array($model->alias . "." . $model->primaryKey => $id
)));
unset($old_value[$model->alias]['created'], $old_value[$model->alias]['modified'], $old_value[$model->alias]['deleted'], $old_value[$model->alias]['deleted_date']);
$change['table_name'] = $model->table;
$change['table_id'] = $id;
$change['mode'] = $mode;
$change['user_id'] = $user_id;
$change['old_value'] = serialize($old_value);
$change['new_value'] = '削除';
$result = $this->saveLog($change);
return $result;
}
private function saveLog($change) {
//使用するモデルに接続
$changeLog = ClassRegistry::init('ChangeLog');
$changeLog->create();
return $changeLog->save($change);
}
//create使わないと自動的にアップデートになるみたい
private function saveId($id) {
//使用するモデルに接続
$changeId = ClassRegistry::init('ChangeLog');
$change['table_id'] = $id;
return $changeId->save($change);
}
}
使用するモデルにChangeLogBehaviorを読みこませる
後は使用するモデルに作成したビヘイビアを読み込ませます。
※SoftDeleteBehaviorを使うことを前提としてます。
//論理削除がbeforeDeleteを使うので先にChangeLogを設定 public $actsAs = array( 'ChangeLog', 'SoftDelete' );
注意はSoftDeleteBehaviorを先に読み込ませておくと
deleteメソッドで削除した時に
ChangeLogBehaviorでdeleteメソッドが読みこめないので
SoftDeleteBehaviorを先に読み込ませておきます。
これで、変更履歴をテーブルにログとして残せておけます。
自分用に作ったので、他の物でうまく機能するかわかりませんが、
ご利用の際は自己責任でお願いします。
GitHubで公開も考えたんですが、
公開の仕方がまだわからないので
とりあえずこちらにコードをさらしました。
(参考)
[CakePHP]データの更新時に自動でデータの差分を取得して履歴テーブルに突っ込むbehavior作った
お世話になった方
twitterで変更履歴ないかなってつぶやいたら
ズボラッカさんがご自身が作られたビヘイビアを紹介してくれました。
@tailtension こんなプラグインを公開しています。どこまで要件を満たすか不明ですがもし興味がわけば使って貰えると嬉しいです! https://t.co/oRNM7UZUnf
— ズボラッカ (@zuborawka) September 24, 2013
ズボラッカさんのビヘイビアもよかったのですが、
機能がそこまでいらなかったので今回は自作しました。
でも、ズボラッカさんのビヘイビアは
後で戻せるってのが魅力的です。
別のサイト作成の時に使わせて頂こうと思います。
ズボラッカさんのビヘイビアの使い方はこちらです。
CakePHP2のバックアッププラグインをGithubに公開してみた
