重要な情報を扱う企業の社内システムやバックエンドのシステムではデータベースの変更履歴を逐一記録するという事は珍しくありません。そのようなシステムでは、性能を犠牲にしてでもいつ・どこで・誰がその情報を更新したのかを記録し、万一のときは情報を復元する手段を持つ事を求められます。このような要求に応えられそうな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 ccpropel-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. まだ書きたいネタがたくさん有るのですが都合により今日より数日間私のエントリ頻度は下がります。

