3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
12 namespace Civi\Core\Transaction
;
17 * @copyright CiviCRM LLC https://civicrm.org/licensing
21 private static $singleton = NULL;
29 * Stack of SQL transactions/savepoints.
31 * @var \Civi\Core\Transaction\Frame[]
38 private $savePointCount = 0;
44 public static function singleton($fresh = FALSE) {
45 if (NULL === self
::$singleton ||
$fresh) {
46 self
::$singleton = new Manager(new \
CRM_Core_DAO());
48 return self
::$singleton;
52 * @param \CRM_Core_DAO $dao
53 * Handle for the DB connection that will execute transaction statements.
54 * (all we really care about is the query() function)
56 public function __construct($dao) {
61 * Increment the transaction count / add a new transaction level
64 * Determines what to do if there's currently an active transaction:.
65 * - If true, then make a new nested transaction ("SAVEPOINT")
66 * - If false, then attach to the existing transaction
68 public function inc($nest = FALSE) {
69 if (!isset($this->frames
[0])) {
70 $frame = $this->createBaseFrame();
71 array_unshift($this->frames
, $frame);
76 $frame = $this->createSavePoint();
77 array_unshift($this->frames
, $frame);
82 $this->frames
[0]->inc();
87 * Decrement the transaction count / close out a transaction level
89 * @throws \CRM_Core_Exception
91 public function dec() {
92 if (!isset($this->frames
[0]) ||
$this->frames
[0]->isEmpty()) {
93 throw new \
CRM_Core_Exception('Transaction integrity error: Expected to find active frame');
96 $this->frames
[0]->dec();
98 if ($this->frames
[0]->isEmpty()) {
99 // Callbacks may cause additional work (such as new transactions),
100 // and it would be confusing if the old frame was still active.
101 // De-register it before calling finish().
102 $oldFrame = array_shift($this->frames
);
108 * Force an immediate rollback, regardless of how many
109 * transaction or frame objects exist.
111 * This is only appropriate when it is _certain_ that the
112 * callstack will not wind-down normally -- e.g. before
115 public function forceRollback() {
116 // we take the long-way-round (rolling back each frame) so that the
117 // internal state of each frame is consistent with its outcome
119 $oldFrames = $this->frames
;
121 foreach ($oldFrames as $oldFrame) {
122 $oldFrame->forceRollback();
127 * Get the (innermost) SQL transaction.
129 * @return \Civi\Core\Transaction\Frame
131 public function getFrame() {
132 return isset($this->frames
[0]) ?
$this->frames
[0] : NULL;
136 * Get the (outermost) SQL transaction (i.e. the one
137 * demarcated by BEGIN/COMMIT/ROLLBACK)
139 * @return \Civi\Core\Transaction\Frame
141 public function getBaseFrame() {
142 if (empty($this->frames
)) {
145 return $this->frames
[count($this->frames
) - 1];
149 * @return \Civi\Core\Transaction\Frame
151 protected function createBaseFrame() {
152 return new Frame($this->dao
, 'BEGIN', 'COMMIT', 'ROLLBACK');
156 * @return \Civi\Core\Transaction\Frame
158 protected function createSavePoint() {
159 $spId = $this->savePointCount++
;
160 return new Frame($this->dao
, "SAVEPOINT civi_{$spId}", NULL, "ROLLBACK TO SAVEPOINT civi_{$spId}");