CakePHPでトランザクション制御

環境: CakePHP 1.3

CakePHPのModelクラスには直接トランザクションを制御するメソッドがない。そこで例えば次のようにAppModelにメソッドを定義する。

class AppModel extends Model {
    function begin() {
        return $this->getDataSource()->begin($this);
    }
    function commit() {
        return $this->getDataSource()->commit($this);
    }
    function rollback() {
        return $this->getDataSource()->rollback($this);
    }
}

しかしこの方法ではテストがやりにくい。明示的にトランザクションを制御したい時というのは、複数のテーブルにまたがる複雑なロジックを組むことが多いので、commit/rollbackされたことを確認するのにいちいちDBの状態を確認していては手間がかかりすぎる。

そもそも「commitされたらそれまでの実行結果が反映される」「rollbackされたらトランザクション開始前の状態に戻る」というのはDBの機能なので、改めてテストし直す意味はない。アプリケーションのテストとしてはcommit/rollbackが発行されたことさえ分かればいい。

そこでAppModelで定義する代わりに、専用のモデルクラスを作ってそこでメソッドの定義を行うことにした。$useTable を false にしてもDataSource自体は取得できるというのがポイントである。

class TransactionManager extends AppModel {
    var $useTable = false;
 
    function begin() {
        return $this->getDataSource()->begin($this);
    }
    function commit() {
        return $this->getDataSource()->commit($this);
    }
    function rollback() {
        return $this->getDataSource()->rollback($this);
    }
}

これでMockを使ったテストができるようになった。トランザクションを使用する際は ClassRegistry 経由でこのモデルを取得する。

$tx = ClassRegistry::init('TransactionManager');
 
if($tx->begin()) {
  //...
 
  if(doSomething() && $tx->commit()) {
    return $values;
  }
}
$tx->rollback();
return false;

テスト時には ClassRegistry にMockのインスタンスを登録しておいて、begin/commit/rollbackの各メソッドが呼び出されたことだけを調べる。もちろんそれぞれの操作が失敗した場合のテストもできる。

function startTest() {
  $this->tx = new MockTransactionManager;
  ClassRegistry::addObject('TransactionManager', $this->tx);
}
 
function testA() {
  $this->tx->expectOnce('begin');
  $this->tx->setReturnValue('begin', true);
  $this->tx->expectOnce('commit');
  $this->tx->setReturnValue('commit', true);
  $this->tx->expectNever('rollback');
 
  //...
}

Leave a Reply