データベースの変更履歴を記録するsfPropelAuditPlugin

重要な情報を扱う企業の社内システムやバックエンドのシステムではデータベースの変更履歴を逐一記録するという事は珍しくありません。そのようなシステムでは、性能を犠牲にしてでもいつ・どこで・誰がその情報を更新したのかを記録し、万一のときは情報を復元する手段を持つ事を求められます。このような要求に応えられそうなsfPropelAuditPluginというプラグインを見つけました。本日はこのsfPropelAuditPluginをつかってこのような要求に応えるシステムの作り方を解説します。(ちなみにAuditとは「監査」という意味です。)

プラグインのインストール

プラグインのインストールはいつも通り簡単、、、とおもいましたがつまずきました。

symfony plugin-install http://plugins.symfony-project.com/sfPropelAuditPlugin

とすると、ダウンロードできませんでした(12/8 16:06現在)仕方がないのでSubversionで対応します。

svn pe svn:externals plugins

エディタが開きますので、下の一行を追加します。

sfPropelAuditPlugin http://svn.symfony-project.com/plugins/sfPropelAuditPlugin

この後、svn updateを実行する事でsfPropelAuditPluginがインストールされます。

propel.ini

もし、まだPropelのbehaviorサポートが有効になっていなければ有効に設定しておきます。

propel.builder.AddBehaviors = true

モデルの再構築

以下のようにして、propel-build-allを行います。

symfony propel-build-all
symfony cc

propel-build-allは、「 propel-build-model → propel-build-sql → propel-insert-sql」をまとめてやる便利コマンドです。 (ちなみにこの後にpropel-load-dataも行うpropel-build-all-loadというコマンドもあります。) いつも通り最後にsymfony ccをしています。

model毎にPropel behaviorを有効にする

有効にするモデルのファイルを開いて末尾に以下のようなコードを追加します。

 sfPropelBehavior::add('モデル名', array('audit'));

使い方

Propel behaviorを有効にしたモデルへの更新(と削除)はすべてsf_auditというテーブルに保存されます。このテーブルはインストール時に行ったprpoel-build-allコマンドの中でsf_auditというテーブルが作成されます。テーブルの構造は以下の通りです。

propel:
  _attributes:        { package: plugins.sfPropelAuditPlugin.lib.model }
  sf_audit:
    _attributes:      { phpName: sfAudit, package: plugins.sfPropelAuditPlugin.lib.model }
    id:                 { phpName: ID, type: integer, required: true, primaryKey: true, autoincrement: true }
    remote_ip_address:  varchar(255)
    object:             varchar(255)
    object_key:         varchar(255)
    object_changes:     longvarchar
    query:              longvarchar
    user:               varchar(255)
    type:               varchar(255)
    created_at:

これらのテーブルにPropel behaviorを有効にしたモデルへのdelete/udpateが記録されます。記録された内容は以下のようになります。

id remote_ip_address object object_key object_changes query user type created_at
1 127.0.0.1 Content NULL INSERT INTO content (BODY,CREATED_AT) VALUES (’aaaaa’,'2006-12-08 19:01:30′) admin INSERT 2006-12-08 19:01:30
2 127.0.0.1 Content 3 a:2:{s:2:”Id”; s:1:”3″; s:4:”Body”; s:6:”bbbbbb”;} UPDATE content SET ID = 3,BODY = ‘bbbbbb’ WHERE content.ID=3 admin UPDATE 2006-12-08 19:04:10
3 127.0.0.1 Content 3 NULL DELETE FROM content WHERE content.ID=3 admin DELETE 2006-12-08 19:04:51

入っているデータについては説明するまでもないですね。

sfGuardPluginと一緒に使う時の問題

sfGuardPlugin便利な認証機構を用意してくれていますがsfPropelAuditPluginと併用すると問題が出る事があります。sfPropelAuditはis_secure: onの環境で使うべきですが、sfGuardPluginのsfBasicSecurityUser()の拡張であるsfGuardSecurityUserクラスは匿名ユーザ(つまりログインしていない)状態で__toString()メソッドを呼ぶと次のような例外をthrowします。

この事がsfPropelAuditPluginの中のsfPropelAuditBehaviorクラスで不都合になるので監査するモデルを匿名ユーザのでも更新(削除)できるようにするために下記のようなパッチを作成しました。

Index: plugins/sfPropelAuditPlugin/lib/sfPropelAuditBehavior.class.php
===================================================================
--- plugins/sfPropelAuditPlugin/lib/sfPropelAuditBehavior.class.php	(revision 2965)
+++ plugins/sfPropelAuditPlugin/lib/sfPropelAuditBehavior.class.php	(working copy)
@@ -204,7 +204,12 @@
         $audit->setObjectKey($object_key);
         $audit->setObjectChanges($changes);
         $audit->setQuery($query);
-        $audit->setUser(sfContext::getInstance()->getUser());
+            try{
+                $user = sfContext::getInstance()->getUser()->__toString();
+            }catch(sfException $e){
+                $user = "unknown user";
+            }
+        $audit->setUser($user);
         $audit->setType($type);
         $audit->setCreatedAt(date($this->date_format));
         $audit->save();

このパッチではsfGuardSecurityUserから出る例外を無効にしています。


折しも2008年4月には日本版SOX法施行され、その変更がいつ誰によってなされたのかを記録する要求は今後ますます増えてくると考えられます(ちょっと大げさです)。sfPropelAuditPluginはそのような要求に素早く(そして、pluggableに)対応可能となります。機会があればお試しあれ。

P.S. まだ書きたいネタがたくさん有るのですが都合により今日より数日間私のエントリ頻度は下がります。

Leave a Reply

You must be logged in to post a comment.