Merge pull request #16067 from eileenmcnaughton/fatal
[civicrm-core.git] / CRM / Core / Transaction.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
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 |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 * @copyright David Strauss <david@fourkitchens.com> (c) 2007
17 * $Id$
18 *
5bb8417a
TO
19 * (Note: This has been considerably rewritten; the interface is preserved
20 * for backward compatibility.)
6a488035 21 *
5bb8417a
TO
22 * Transaction management in Civi is divided among three classes:
23 * - CRM_Core_Transaction: API. This binds to __construct() + __destruct()
24 * and notifies the transaction manager when it's OK to begin/end a transaction.
25 * - Civi\Core\Transaction\Manager: Tracks pending transaction-frames
26 * - Civi\Core\Transaction\Frame: A nestable transaction (e.g. based on BEGIN/COMMIT/ROLLBACK
27 * or SAVEPOINT/ROLLBACK TO SAVEPOINT).
28 *
29 * Examples:
30 *
31 * @code
32 * // Some business logic using the helper functions
33 * function my_business_logic() {
34 * CRM_Core_Transaction::create()->run(function($tx) {
35 * ...do work...
36 * if ($error) throw new Exception();
37 * });
38 * }
39 *
40 * // Some business logic which returns an error-value
41 * // and explicitly manages the transaction.
42 * function my_business_logic() {
43 * $tx = new CRM_Core_Transaction();
44 * ...do work...
45 * if ($error) {
46 * $tx->rollback();
47 * return error_value;
48 * }
49 * }
50 *
51 * // Some business logic which uses exceptions
52 * // and explicitly manages the transaction.
53 * function my_business_logic() {
54 * $tx = new CRM_Core_Transaction();
55 * try {
56 * ...do work...
57 * } catch (Exception $ex) {
58 * $tx->rollback()->commit();
59 * throw $ex;
60 * }
61 * }
62 *
63 * @endcode
64 *
65 * Note: As of 4.6, the transaction manager supports both reference-counting and nested
66 * transactions (SAVEPOINTs). In the past, it only supported reference-counting. The two cases
67 * may exhibit different systemic effects with respect to unhandled exceptions.
6a488035
TO
68 */
69class CRM_Core_Transaction {
70
71 /**
66f9e52b 72 * These constants represent phases at which callbacks can be invoked.
6a488035 73 */
7da04cde
TO
74 const PHASE_PRE_COMMIT = 1;
75 const PHASE_POST_COMMIT = 2;
76 const PHASE_PRE_ROLLBACK = 4;
77 const PHASE_POST_ROLLBACK = 8;
6a488035
TO
78
79 /**
5bb8417a
TO
80 * Whether commit() has been called on this instance
81 * of CRM_Core_Transaction
518fa0ee 82 * @var bool
6a488035 83 */
5bb8417a 84 private $_pseudoCommitted = FALSE;
6a488035
TO
85
86 /**
66f9e52b 87 * Ensure that an SQL transaction is started.
6a488035 88 *
5bb8417a 89 * This is a thin wrapper around __construct() which allows more fluent coding.
6a488035 90 *
6a0b768e
TO
91 * @param bool $nest
92 * Determines what to do if there's currently an active transaction:.
5bb8417a
TO
93 * - If true, then make a new nested transaction ("SAVEPOINT")
94 * - If false, then attach to the existing transaction
95 * @return \CRM_Core_Transaction
6a488035 96 */
5bb8417a
TO
97 public static function create($nest = FALSE) {
98 return new self($nest);
99 }
a0ee3941
EM
100
101 /**
66f9e52b 102 * Ensure that an SQL transaction is started.
a0ee3941 103 *
6a0b768e
TO
104 * @param bool $nest
105 * Determines what to do if there's currently an active transaction:.
5bb8417a
TO
106 * - If true, then make a new nested transaction ("SAVEPOINT")
107 * - If false, then attach to the existing transaction
a0ee3941 108 */
00be9182 109 public function __construct($nest = FALSE) {
5bb8417a 110 \Civi\Core\Transaction\Manager::singleton()->inc($nest);
6a488035
TO
111 }
112
00be9182 113 public function __destruct() {
6a488035
TO
114 $this->commit();
115 }
116
5bb8417a 117 /**
66f9e52b 118 * Immediately commit or rollback.
5bb8417a
TO
119 *
120 * (Note: Prior to 4.6, return void)
121 *
122 * @return \CRM_Core_Exception this
123 */
00be9182 124 public function commit() {
5bb8417a 125 if (!$this->_pseudoCommitted) {
6a488035 126 $this->_pseudoCommitted = TRUE;
5bb8417a 127 \Civi\Core\Transaction\Manager::singleton()->dec();
6a488035 128 }
5bb8417a 129 return $this;
6a488035
TO
130 }
131
a0ee3941
EM
132 /**
133 * @param $flag
134 */
518fa0ee 135 public static function rollbackIfFalse($flag) {
5bb8417a
TO
136 $frame = \Civi\Core\Transaction\Manager::singleton()->getFrame();
137 if ($flag === FALSE && $frame !== NULL) {
138 $frame->setRollbackOnly();
6a488035
TO
139 }
140 }
141
5bb8417a
TO
142 /**
143 * Mark the transaction for rollback.
144 *
145 * (Note: Prior to 4.6, return void)
ba3228d1 146 * @return \CRM_Core_Transaction
5bb8417a 147 */
6a488035 148 public function rollback() {
5bb8417a
TO
149 $frame = \Civi\Core\Transaction\Manager::singleton()->getFrame();
150 if ($frame !== NULL) {
151 $frame->setRollbackOnly();
152 }
153 return $this;
154 }
155
156 /**
157 * Execute a function ($callable) within the scope of a transaction. If
158 * $callable encounters an unhandled exception, then rollback the transaction.
159 *
160 * After calling run(), the CRM_Core_Transaction object is "used up"; do not
161 * use it again.
162 *
6a0b768e 163 * @param string $callable
b44e3f84 164 * Should exception one parameter (CRM_Core_Transaction $tx).
ba3228d1 165 * @return CRM_Core_Transaction
5bb8417a
TO
166 * @throws Exception
167 */
168 public function run($callable) {
169 try {
170 $callable($this);
0db6c3e1
TO
171 }
172 catch (Exception $ex) {
5bb8417a
TO
173 $this->rollback()->commit();
174 throw $ex;
175 }
176 $this->commit();
177 return $this;
6a488035
TO
178 }
179
180 /**
181 * Force an immediate rollback, regardless of how many any
182 * CRM_Core_Transaction objects are waiting for
183 * pseudo-commits.
184 *
185 * Only rollback if the transaction API has been called.
186 *
187 * This is only appropriate when it is _certain_ that the
188 * callstack will not wind-down normally -- e.g. before
189 * a call to exit().
190 */
518fa0ee 191 public static function forceRollbackIfEnabled() {
5bb8417a
TO
192 if (\Civi\Core\Transaction\Manager::singleton()->getFrame() !== NULL) {
193 \Civi\Core\Transaction\Manager::singleton()->forceRollback();
6a488035
TO
194 }
195 }
196
a0ee3941
EM
197 /**
198 * @return bool
199 */
518fa0ee 200 public static function willCommit() {
5bb8417a
TO
201 $frame = \Civi\Core\Transaction\Manager::singleton()->getFrame();
202 return ($frame === NULL) ? TRUE : !$frame->isRollbackOnly();
6a488035
TO
203 }
204
205 /**
66f9e52b 206 * Determine whether there is a pending transaction.
6a488035 207 */
518fa0ee 208 public static function isActive() {
5bb8417a
TO
209 $frame = \Civi\Core\Transaction\Manager::singleton()->getFrame();
210 return ($frame !== NULL);
6a488035
TO
211 }
212
213 /**
66f9e52b 214 * Add a transaction callback.
6a488035 215 *
5bb8417a
TO
216 * Note: It's conceivable to add callbacks to the main/overall transaction
217 * (aka $manager->getBaseFrame()) or to the innermost nested transaction
218 * (aka $manager->getFrame()). addCallback() has been used in the past to
219 * work-around deadlocks. This may or may not be necessary now -- but it
220 * seems more consistent (for b/c purposes) to attach callbacks to the
221 * main/overall transaction.
222 *
6a488035
TO
223 * Pre-condition: isActive()
224 *
6a0b768e
TO
225 * @param int $phase
226 * A constant; one of: self::PHASE_{PRE,POST}_{COMMIT,ROLLBACK}.
227 * @param string $callback
228 * A PHP callback.
229 * @param mixed $params
230 * Optional values to pass to callback.
6a488035 231 * See php manual call_user_func_array for details.
ba3228d1 232 * @param int $id
6a488035 233 */
518fa0ee 234 public static function addCallback($phase, $callback, $params = NULL, $id = NULL) {
5bb8417a
TO
235 $frame = \Civi\Core\Transaction\Manager::singleton()->getBaseFrame();
236 $frame->addCallback($phase, $callback, $params, $id);
6a488035 237 }
96025800 238
6a488035 239}