トランザクションがモデル層に属さないと私が言う理由は、基本的に次のとおりです。
モデルは、他のモデルのメソッドを呼び出すことができます。
モデルがトランザクションを開始しようとしたが、呼び出し元が既にトランザクションを開始したかどうかを認識していない場合、 @Bubba の回答のコード例に示すように、モデルは条件付きでトランザクションを開始する必要があります。モデルのメソッドは、呼び出し元が独自のトランザクションの開始が許可されているかどうかを伝えることができるように、フラグを受け入れる必要があります。または、モデルは呼び出し元の「トランザクション中」の状態を照会する機能を備えている必要があります。
public function setPrivacy($privacy, $caller){
if (! $caller->isInTransaction() ) $this->beginTransaction();
$this->privacy = $privacy;
// ...action code..
if (! $caller->isInTransaction() ) $this->commit();
}
呼び出し元がオブジェクトでない場合はどうなりますか? PHP では、静的メソッドまたは単に非オブジェクト指向のコードである可能性があります。これは非常に面倒になり、モデル内で多くのコードが繰り返されることになります。
これはControl Couplingの例でもあります。これは、呼び出し元が呼び出されたオブジェクトの内部動作について何かを知っている必要があるため、悪いと見なされます。たとえば、モデルの一部のメソッドには $transactional パラメーターがある場合がありますが、他のメソッドにはそのパラメーターがない場合があります。パラメータが重要な場合、呼び出し元はどのように知る必要がありますか?
// I need to override method's attempt to commit
$video->setPrivacy($privacy, false);
// But I have no idea if this method might attempt to commit
$video->setFormat($format);
私が提案した (または Propel のようないくつかのフレームワークで実装された) もう 1 つの解決策はbeginTransaction()
、commit()
DBAL が既にトランザクション内にあることを認識している場合に、何もしないことです。ただし、モデルがコミットしようとして、実際にはコミットされていないことが判明した場合、これは異常につながる可能性があります。または、ロールバックを試みて、その要求を無視します。私は以前にこれらの異常について書きました。
私が提案した妥協点は、モデルがトランザクションについて知らないということです。モデルは、その要求がsetPrivacy()
すぐにコミットする必要があるものなのか、それともより大きな全体像 (複数のモデルが関与するより複雑な一連の変更) の一部であり、これらすべての変更が成功した場合に のみコミットする必要があるのかを認識していません。それが取引のポイントです。
モデルが自分のトランザクションを開始してコミットできるかどうか、またはコミットする必要があるかどうかがわからない場合、誰がそれを行うのでしょうか? GRASP には、ユース ケースの非 UI クラスであるコントローラー パターンが含まれており、そのユース ケースを達成するためにすべての部分を作成および制御する責任が割り当てられています。 コントローラーはトランザクションについて知っています。これは、完全なユースケースが複雑であるかどうか、モデルで複数の変更を 1 つのトランザクション内 (または複数のトランザクション内) で行う必要があるかどうかに関するすべての情報にアクセスできる場所であるためです。
以前に書いたbeforeAction()
、MVC コントローラーのメソッドでトランザクションを開始し、メソッドでコミットするという例afterAction()
は単純化したものです。コントローラーは、現在のアクションを完了するために論理的に必要な数のトランザクションを自由に開始およびコミットできる必要があります。または、コントローラーが明示的なトランザクション制御を控え、モデルが各変更を自動コミットできるようにする場合もあります。
しかし重要なのは、どのトランザクションが必要であるかについての情報は、モデルが知らないものだということです。モデルは ($transactional パラメータの形式で) 伝えられるか、呼び出し元からクエリを実行する必要があります。とにかく、コントローラのアクションまで質問を委任する必要があります。
また、このような複雑なユースケースを実行する方法と、すべての変更を単一のトランザクションに含めるかどうかをそれぞれが知っているクラスのサービス層を作成することもできます。そうすれば、多くのコードの繰り返しを避けることができます。しかし、PHP アプリに個別のサービス レイヤーを含めることは一般的ではありません。通常、コントローラーのアクションはサービス層と一致します。