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