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 * @var array<Frame> stack of SQL transactions/savepoints
36 private $savePointCount = 0;
42 public static function singleton($fresh = FALSE) {
43 if (NULL === self
::$singleton ||
$fresh) {
44 self
::$singleton = new Manager(new \
CRM_Core_DAO());
46 return self
::$singleton;
50 * @param \CRM_Core_DAO $dao
51 * Handle for the DB connection that will execute transaction statements.
52 * (all we really care about is the query() function)
54 public function __construct($dao) {
59 * Increment the transaction count / add a new transaction level
62 * Determines what to do if there's currently an active transaction:.
63 * - If true, then make a new nested transaction ("SAVEPOINT")
64 * - If false, then attach to the existing transaction
66 public function inc($nest = FALSE) {
67 if (!isset($this->frames
[0])) {
68 $frame = $this->createBaseFrame();
69 array_unshift($this->frames
, $frame);
74 $frame = $this->createSavePoint();
75 array_unshift($this->frames
, $frame);
80 $this->frames
[0]->inc();
85 * Decrement the transaction count / close out a transaction level
87 * @throws \CRM_Core_Exception
89 public function dec() {
90 if (!isset($this->frames
[0]) ||
$this->frames
[0]->isEmpty()) {
91 throw new \
CRM_Core_Exception('Transaction integrity error: Expected to find active frame');
94 $this->frames
[0]->dec();
96 if ($this->frames
[0]->isEmpty()) {
97 // Callbacks may cause additional work (such as new transactions),
98 // and it would be confusing if the old frame was still active.
99 // De-register it before calling finish().
100 $oldFrame = array_shift($this->frames
);
106 * Force an immediate rollback, regardless of how many
107 * transaction or frame objects exist.
109 * This is only appropriate when it is _certain_ that the
110 * callstack will not wind-down normally -- e.g. before
113 public function forceRollback() {
114 // we take the long-way-round (rolling back each frame) so that the
115 // internal state of each frame is consistent with its outcome
117 $oldFrames = $this->frames
;
119 foreach ($oldFrames as $oldFrame) {
120 $oldFrame->forceRollback();
125 * Get the (innermost) SQL transaction.
127 * @return \Civi\Core\Transaction\Frame
129 public function getFrame() {
130 return isset($this->frames
[0]) ?
$this->frames
[0] : NULL;
134 * Get the (outermost) SQL transaction (i.e. the one
135 * demarcated by BEGIN/COMMIT/ROLLBACK)
137 * @return \Civi\Core\Transaction\Frame
139 public function getBaseFrame() {
140 if (empty($this->frames
)) {
143 return $this->frames
[count($this->frames
) - 1];
147 * @return \Civi\Core\Transaction\Frame
149 protected function createBaseFrame() {
150 return new Frame($this->dao
, 'BEGIN', 'COMMIT', 'ROLLBACK');
154 * @return \Civi\Core\Transaction\Frame
156 protected function createSavePoint() {
157 $spId = $this->savePointCount++
;
158 return new Frame($this->dao
, "SAVEPOINT civi_{$spId}", NULL, "ROLLBACK TO SAVEPOINT civi_{$spId}");