Merge pull request #16008 from civicrm/5.20
[civicrm-core.git] / Civi / Core / Transaction / Frame.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
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 +--------------------------------------------------------------------+
10 */
11
12 namespace Civi\Core\Transaction;
13
14 /**
15 * A "frame" is a layer in a series of nested transactions. Generally,
16 * the outermost frame is a normal SQL transaction (BEGIN/ROLLBACK/COMMIT)
17 * and any nested frames are SQL savepoints (SAVEPOINT foo/ROLLBACK TO SAVEPOINT).
18 *
19 * @package Civi
20 * @copyright CiviCRM LLC https://civicrm.org/licensing
21 */
22 class Frame {
23
24 const F_NEW = 0, F_ACTIVE = 1, F_DONE = 2, F_FORCED = 3;
25
26 /**
27 * @var \CRM_Core_DAO
28 */
29 private $dao;
30
31 /**
32 * @var string|null e.g. "BEGIN" or "SAVEPOINT foo"
33 */
34 private $beginStmt;
35
36 /**
37 * @var string|null e.g. "COMMIT"
38 */
39 private $commitStmt;
40
41 /**
42 * @var string|null e.g. "ROLLBACK" or "ROLLBACK TO SAVEPOINT foo"
43 */
44 private $rollbackStmt;
45
46 /**
47 * @var int
48 */
49 private $refCount = 0;
50 private $callbacks;
51 private $doCommit = TRUE;
52
53 /**
54 * @var int
55 */
56 private $state = self::F_NEW;
57
58 /**
59 * @param \CRM_Core_DAO $dao
60 * @param string|null $beginStmt e.g. "BEGIN" or "SAVEPOINT foo"
61 * @param string|null $commitStmt e.g. "COMMIT"
62 * @param string|null $rollbackStmt e.g. "ROLLBACK" or "ROLLBACK TO SAVEPOINT foo"
63 */
64 public function __construct($dao, $beginStmt, $commitStmt, $rollbackStmt) {
65 $this->dao = $dao;
66 $this->beginStmt = $beginStmt;
67 $this->commitStmt = $commitStmt;
68 $this->rollbackStmt = $rollbackStmt;
69
70 $this->callbacks = [
71 \CRM_Core_Transaction::PHASE_PRE_COMMIT => [],
72 \CRM_Core_Transaction::PHASE_POST_COMMIT => [],
73 \CRM_Core_Transaction::PHASE_PRE_ROLLBACK => [],
74 \CRM_Core_Transaction::PHASE_POST_ROLLBACK => [],
75 ];
76 }
77
78 public function inc() {
79 $this->refCount++;
80 }
81
82 public function dec() {
83 $this->refCount--;
84 }
85
86 /**
87 * @return bool
88 */
89 public function isEmpty() {
90 return ($this->refCount == 0);
91 }
92
93 /**
94 * @return bool
95 */
96 public function isRollbackOnly() {
97 return !$this->doCommit;
98 }
99
100 public function setRollbackOnly() {
101 $this->doCommit = FALSE;
102 }
103
104 /**
105 * Begin frame processing.
106 *
107 * @throws \CRM_Core_Exception
108 */
109 public function begin() {
110 if ($this->state !== self::F_NEW) {
111 throw new \CRM_Core_Exception('State is not F_NEW');
112 };
113
114 $this->state = self::F_ACTIVE;
115 if ($this->beginStmt) {
116 $this->dao->query($this->beginStmt);
117 }
118 }
119
120 /**
121 * Finish frame processing.
122 *
123 * @param int $newState
124 *
125 * @throws \CRM_Core_Exception
126 */
127 public function finish($newState = self::F_DONE) {
128 if ($this->state == self::F_FORCED) {
129 return;
130 }
131 if ($this->state !== self::F_ACTIVE) {
132 throw new \CRM_Core_Exception('State is not F_ACTIVE');
133 };
134
135 $this->state = $newState;
136
137 if ($this->doCommit) {
138 $this->invokeCallbacks(\CRM_Core_Transaction::PHASE_PRE_COMMIT);
139 if ($this->commitStmt) {
140 $this->dao->query($this->commitStmt);
141 }
142 $this->invokeCallbacks(\CRM_Core_Transaction::PHASE_POST_COMMIT);
143 }
144 else {
145 $this->invokeCallbacks(\CRM_Core_Transaction::PHASE_PRE_ROLLBACK);
146 if ($this->rollbackStmt) {
147 $this->dao->query($this->rollbackStmt);
148 }
149 $this->invokeCallbacks(\CRM_Core_Transaction::PHASE_POST_ROLLBACK);
150 }
151 }
152
153 public function forceRollback() {
154 $this->setRollbackOnly();
155 $this->finish(self::F_FORCED);
156 }
157
158 /**
159 * Add a transaction callback.
160 *
161 * Pre-condition: isActive()
162 *
163 * @param int $phase
164 * A constant; one of: self::PHASE_{PRE,POST}_{COMMIT,ROLLBACK}.
165 * @param mixed $callback
166 * A PHP callback.
167 * @param array|NULL $params Optional values to pass to callback.
168 * See php manual call_user_func_array for details.
169 * @param null $id
170 */
171 public function addCallback($phase, $callback, $params = NULL, $id = NULL) {
172 if ($id) {
173 $this->callbacks[$phase][$id] = [
174 'callback' => $callback,
175 'parameters' => (is_array($params) ? $params : [$params]),
176 ];
177 }
178 else {
179 $this->callbacks[$phase][] = [
180 'callback' => $callback,
181 'parameters' => (is_array($params) ? $params : [$params]),
182 ];
183 }
184 }
185
186 /**
187 * @param int $phase
188 */
189 public function invokeCallbacks($phase) {
190 if (is_array($this->callbacks[$phase])) {
191 foreach ($this->callbacks[$phase] as $cb) {
192 call_user_func_array($cb['callback'], $cb['parameters']);
193 }
194 }
195 }
196
197 }