Doctrineで論理削除を意識せずに扱う

こんにちは。小川です。

本日はDoctrineで論理削除を意識せずに扱う方法を紹介しようと思います。
結論から言うと、Doctrineが標準で用意しているSoftDeleteテンプレートをモデルに実装すれば自動的にクエリが発行する際に削除の判定を行ってくれるようになります。

テンプレートに関しては以前書いたブログがあるのでそちらを参考にしてください。
ただ上記の記事はDoctrine 0.11をベースに書いた記事なのですが、Doctrine 1.0からテンプレートの定義方法が少し変わったので先に少し説明します。

以前書いた記事では、config/doctrine/schema.ymlで


templates:
  ...

のようにtemplatesという名前で指定していましたが、templatesではなく「actAs」で指定するようになりました。
actAs自体は以前から存在しており、内容自体はほぼ同じです。

ちなみに以前書いたときはDoctrine_Template_Timestampableのように完全なクラス名を指定したのですが、クラスを生成する前にDoctrine_Template_で始まるクラスがあればそちらを使うような仕組みになっているためTimestampableと指定するだけでいいようです。

というわけで、論理削除をしたいモデルに対してschema.ymlで


Article:
  columns:
    ...
  actAs
    SoftDelete: {}

上記のように記述すればSoftDeleteテンプレートが実装されます。

それではSoftDeleteテンプレートがどのようなことを行うのかを少し説明します。
SoftDeleteテンプレートを実装したモデルは自動的に


  deleted: { type: boolean, notnull: true, default: false }

deletedカラムがモデルに追加され、trueの場合は削除として扱われるようになります。

検索時は、Doctrine_Queryのexecuteメソッドが実行されたタイミングで、(deleted = false OR deleted IS NULL)の条件が追加されます。もちろんエイリアスも張られます。

Doctrine_Tableに実装されているファインダメソッド(findXXX)や$article->getComments()などとやったときも内部でDoctrine_Queryが生成されるので
Doctrineのオブジェクトを取得する場合は大体大丈夫なのではないかと思います。

削除のときはDoctrine_QueryでDELETE文を発行する方法とRecordオブジェクトにdeleteメソッドを発行する方法が存在しますが、どちらの場合でもdeletedをtrueにUPDATEし、DELETEは行わないようになっています。

また、削除済みのものも検索で取得したい場合や物理削除を行いたい場合もあると思います。
これに関してはきれいな形での対応はされていないのですが、どちらもDoctrine_Queryでクエリを発行した場合にクエリにdeletedに関する記述がある場合はクエリを修正しないように作成されているため、


$query->addWhere("{$alias}.deleted IS NOT NULL")

などの指定を追加してあげれば削除済みのレコードの検索や物理削除を行えます。
ただしこのときに


Doctrine_Query::create()->from('Article')
  ->where('deleted IS NOT NULL')->execute();
// DQL => "FROM Article WHERE deleted IS NOT NULL"

と書いた場合、クエリ中から"Article.deleted"を検索し記述がないものとして処理されるため、必ずエイリアスをつける必要があります。
ただしこれはSELECT限定で、DELETEのときはエイリアスがなくても動くようです。

ここまで実装方法などについて書いてきましたが、実はこれだけだと動きません。
Doctrineのデフォルトの設定では、Doctrine_Queryを実行前後で修正することが出来なくなっており、そこを設定してあげる必要があります。この設定の方法がsymfony 1.0とsymfony 1.1で変わってきます。

symfony 1.1ではProjectConfigurationクラスに設定を記述します。


<?php
class ProjectConfiguration extends sfProjectConfiguration
{
  protected $plugins = array('sfDoctrinePlugin');
  public function configureDoctrine($manager)
  {
    $manager->setAttribute(Doctrine::ATTR_USE_DQL_CALLBACKS, true);
  }
}

上記のようにconfigureDoctrineメソッドでDoctrine::ATTR_USE_DQL_CALLBACKSをtrueにセットしなければいけません。
symfony 1.2でも同様です。というかsymfony 1.2でやったので上記がsymfony 1.1で動くかは実は検証してなかったりしますが、たぶん大丈夫だと思います。

symfony 1.0ではProjectConfigurationクラスはないのでどのようにやるかというと、sfDoctrinePlugin/configディレクトリの中にあるdoctrine.ymlをconfig直下にコピーしてきてそこに記述します。


all:
  attributes:
    use_dql_callbacks: true

attributes内にuse_dql_callbacksをtrueにする記述を追加すればOKです。ぶっちゃけこれも検証してないですがたぶん大丈夫だと思います。

SoftDeleteテンプレートについての説明は以上です。論理削除は全て自前で実装すると何かと面倒ですが、このテンプレートを使えばかなり楽になりそうですね。
Doctrineで論理削除を行う場合はぜひ使ってみてはいかがでしょうか。