スポンサードリンク

CakePHP
CakePHP

変更履歴を自動で取れる仕組みが欲しかったので

ビヘイビアで実装してみました。

初めてビヘイビア作ったのでハラハラしました。

スポンサードリンク

変更履歴のイメージ

更新した時に

いつ誰がどのテーブルを変更してたのかが分かるように

更新前と更新後のデータを残しておく

といった感じで作成しました。

ログを取るためように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で変更履歴ないかなってつぶやいたら

ズボラッカさんがご自身が作られたビヘイビアを紹介してくれました。

ズボラッカさんのビヘイビアもよかったのですが、

機能がそこまでいらなかったので今回は自作しました。

でも、ズボラッカさんのビヘイビアは

後で戻せるってのが魅力的です。

別のサイト作成の時に使わせて頂こうと思います。

ズボラッカさんのビヘイビアの使い方はこちらです。
CakePHP2のバックアッププラグインをGithubに公開してみた

スポンサードリンク