Merge pull request #16379 from totten/master-coder-ivt
[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 * The statement used to start this transaction - e.g. "BEGIN" or "SAVEPOINT foo"
33 *
34 * @var string|null
35 */
36 private $beginStmt;
37
38 /**
39 * The statement used to commit this transaction - e.g. "COMMIT"
40 *
41 * @var string|null
42 */
43 private $commitStmt;
44
45 /**
46 * The statement used to rollback this transaction - e.g. "ROLLBACK" or "ROLLBACK TO SAVEPOINT foo"
47 *
48 * @var string|null
49 */
50 private $rollbackStmt;
51
52 /**
53 * @var int
54 */
55 private $refCount = 0;
56 private $callbacks;
57 private $doCommit = TRUE;
58
59 /**
60 * @var int
61 */
62 private $state = self::F_NEW;
63
64 /**
65 * @param \CRM_Core_DAO $dao
66 * @param string|null $beginStmt e.g. "BEGIN" or "SAVEPOINT foo"
67 * @param string|null $commitStmt e.g. "COMMIT"
68 * @param string|null $rollbackStmt e.g. "ROLLBACK" or "ROLLBACK TO SAVEPOINT foo"
69 */
70 public function __construct($dao, $beginStmt, $commitStmt, $rollbackStmt) {
71 $this->dao = $dao;
72 $this->beginStmt = $beginStmt;
73 $this->commitStmt = $commitStmt;
74 $this->rollbackStmt = $rollbackStmt;
75
76 $this->callbacks = [
77 \CRM_Core_Transaction::PHASE_PRE_COMMIT => [],
78 \CRM_Core_Transaction::PHASE_POST_COMMIT => [],
79 \CRM_Core_Transaction::PHASE_PRE_ROLLBACK => [],
80 \CRM_Core_Transaction::PHASE_POST_ROLLBACK => [],
81 ];
82 }
83
84 public function inc() {
85 $this->refCount++;
86 }
87
88 public function dec() {
89 $this->refCount--;
90 }
91
92 /**
93 * @return bool
94 */
95 public function isEmpty() {
96 return ($this->refCount == 0);
97 }
98
99 /**
100 * @return bool
101 */
102 public function isRollbackOnly() {
103 return !$this->doCommit;
104 }
105
106 public function setRollbackOnly() {
107 $this->doCommit = FALSE;
108 }
109
110 /**
111 * Begin frame processing.
112 *
113 * @throws \CRM_Core_Exception
114 */
115 public function begin() {
116 if ($this->state !== self::F_NEW) {
117 throw new \CRM_Core_Exception('State is not F_NEW');
118 };
119
120 $this->state = self::F_ACTIVE;
121 if ($this->beginStmt) {
122 $this->dao->query($this->beginStmt);
123 }
124 }
125
126 /**
127 * Finish frame processing.
128 *
129 * @param int $newState
130 *
131 * @throws \CRM_Core_Exception
132 */
133 public function finish($newState = self::F_DONE) {
134 if ($this->state == self::F_FORCED) {
135 return;
136 }
137 if ($this->state !== self::F_ACTIVE) {
138 throw new \CRM_Core_Exception('State is not F_ACTIVE');
139 };
140
141 $this->state = $newState;
142
143 if ($this->doCommit) {
144 $this->invokeCallbacks(\CRM_Core_Transaction::PHASE_PRE_COMMIT);
145 if ($this->commitStmt) {
146 $this->dao->query($this->commitStmt);
147 }
148 $this->invokeCallbacks(\CRM_Core_Transaction::PHASE_POST_COMMIT);
149 }
150 else {
151 $this->invokeCallbacks(\CRM_Core_Transaction::PHASE_PRE_ROLLBACK);
152 if ($this->rollbackStmt) {
153 $this->dao->query($this->rollbackStmt);
154 }
155 $this->invokeCallbacks(\CRM_Core_Transaction::PHASE_POST_ROLLBACK);
156 }
157 }
158
159 public function forceRollback() {
160 $this->setRollbackOnly();
161 $this->finish(self::F_FORCED);
162 }
163
164 /**
165 * Add a transaction callback.
166 *
167 * Pre-condition: isActive()
168 *
169 * @param int $phase
170 * A constant; one of: self::PHASE_{PRE,POST}_{COMMIT,ROLLBACK}.
171 * @param mixed $callback
172 * A PHP callback.
173 * @param array|NULL $params Optional values to pass to callback.
174 * See php manual call_user_func_array for details.
175 * @param null $id
176 */
177 public function addCallback($phase, $callback, $params = NULL, $id = NULL) {
178 if ($id) {
179 $this->callbacks[$phase][$id] = [
180 'callback' => $callback,
181 'parameters' => (is_array($params) ? $params : [$params]),
182 ];
183 }
184 else {
185 $this->callbacks[$phase][] = [
186 'callback' => $callback,
187 'parameters' => (is_array($params) ? $params : [$params]),
188 ];
189 }
190 }
191
192 /**
193 * @param int $phase
194 */
195 public function invokeCallbacks($phase) {
196 if (is_array($this->callbacks[$phase])) {
197 foreach ($this->callbacks[$phase] as $cb) {
198 call_user_func_array($cb['callback'], $cb['parameters']);
199 }
200 }
201 }
202
203 }