3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2019
32 * @copyright David Strauss <david@fourkitchens.com> (c) 2007
35 * (Note: This has been considerably rewritten; the interface is preserved
36 * for backward compatibility.)
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).
48 * // Some business logic using the helper functions
49 * function my_business_logic() {
50 * CRM_Core_Transaction::create()->run(function($tx) {
52 * if ($error) throw new Exception();
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();
67 * // Some business logic which uses exceptions
68 * // and explicitly manages the transaction.
69 * function my_business_logic() {
70 * $tx = new CRM_Core_Transaction();
73 * } catch (Exception $ex) {
74 * $tx->rollback()->commit();
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.
85 class CRM_Core_Transaction
{
88 * These constants represent phases at which callbacks can be invoked.
90 const PHASE_PRE_COMMIT
= 1;
91 const PHASE_POST_COMMIT
= 2;
92 const PHASE_PRE_ROLLBACK
= 4;
93 const PHASE_POST_ROLLBACK
= 8;
96 * Whether commit() has been called on this instance
97 * of CRM_Core_Transaction
100 private $_pseudoCommitted = FALSE;
103 * Ensure that an SQL transaction is started.
105 * This is a thin wrapper around __construct() which allows more fluent coding.
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
113 public static function create($nest = FALSE) {
114 return new self($nest);
118 * Ensure that an SQL transaction is started.
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
125 public function __construct($nest = FALSE) {
126 \Civi\Core\Transaction\Manager
::singleton()->inc($nest);
129 public function __destruct() {
134 * Immediately commit or rollback.
136 * (Note: Prior to 4.6, return void)
138 * @return \CRM_Core_Exception this
140 public function commit() {
141 if (!$this->_pseudoCommitted
) {
142 $this->_pseudoCommitted
= TRUE;
143 \Civi\Core\Transaction\Manager
::singleton()->dec();
151 public static function rollbackIfFalse($flag) {
152 $frame = \Civi\Core\Transaction\Manager
::singleton()->getFrame();
153 if ($flag === FALSE && $frame !== NULL) {
154 $frame->setRollbackOnly();
159 * Mark the transaction for rollback.
161 * (Note: Prior to 4.6, return void)
162 * @return \CRM_Core_Transaction
164 public function rollback() {
165 $frame = \Civi\Core\Transaction\Manager
::singleton()->getFrame();
166 if ($frame !== NULL) {
167 $frame->setRollbackOnly();
173 * Execute a function ($callable) within the scope of a transaction. If
174 * $callable encounters an unhandled exception, then rollback the transaction.
176 * After calling run(), the CRM_Core_Transaction object is "used up"; do not
179 * @param string $callable
180 * Should exception one parameter (CRM_Core_Transaction $tx).
181 * @return CRM_Core_Transaction
184 public function run($callable) {
188 catch (Exception
$ex) {
189 $this->rollback()->commit();
197 * Force an immediate rollback, regardless of how many any
198 * CRM_Core_Transaction objects are waiting for
201 * Only rollback if the transaction API has been called.
203 * This is only appropriate when it is _certain_ that the
204 * callstack will not wind-down normally -- e.g. before
207 public static function forceRollbackIfEnabled() {
208 if (\Civi\Core\Transaction\Manager
::singleton()->getFrame() !== NULL) {
209 \Civi\Core\Transaction\Manager
::singleton()->forceRollback();
216 public static function willCommit() {
217 $frame = \Civi\Core\Transaction\Manager
::singleton()->getFrame();
218 return ($frame === NULL) ?
TRUE : !$frame->isRollbackOnly();
222 * Determine whether there is a pending transaction.
224 public static function isActive() {
225 $frame = \Civi\Core\Transaction\Manager
::singleton()->getFrame();
226 return ($frame !== NULL);
230 * Add a transaction callback.
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.
239 * Pre-condition: isActive()
242 * A constant; one of: self::PHASE_{PRE,POST}_{COMMIT,ROLLBACK}.
243 * @param string $callback
245 * @param mixed $params
246 * Optional values to pass to callback.
247 * See php manual call_user_func_array for details.
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);