INFRA-132 - Put "else" and "catch" on new line
[civicrm-core.git] / CRM / Core / Transaction.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
39de6fd5 4 | CiviCRM version 4.6 |
6a488035 5 +--------------------------------------------------------------------+
06b69b18 6 | Copyright CiviCRM LLC (c) 2004-2014 |
6a488035
TO
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
06b69b18 31 * @copyright CiviCRM LLC (c) 2004-2014
6a488035
TO
32 * @copyright David Strauss <david@fourkitchens.com> (c) 2007
33 * $Id$
34 *
5bb8417a
TO
35 * (Note: This has been considerably rewritten; the interface is preserved
36 * for backward compatibility.)
6a488035 37 *
5bb8417a
TO
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.
6a488035
TO
84 */
85class CRM_Core_Transaction {
86
87 /**
88 * These constants represent phases at which callbacks can be invoked
89 */
7da04cde
TO
90 const PHASE_PRE_COMMIT = 1;
91 const PHASE_POST_COMMIT = 2;
92 const PHASE_PRE_ROLLBACK = 4;
93 const PHASE_POST_ROLLBACK = 8;
6a488035
TO
94
95 /**
5bb8417a
TO
96 * Whether commit() has been called on this instance
97 * of CRM_Core_Transaction
6a488035 98 */
5bb8417a 99 private $_pseudoCommitted = FALSE;
6a488035
TO
100
101 /**
5bb8417a 102 * Ensure that an SQL transaction is started
6a488035 103 *
5bb8417a 104 * This is a thin wrapper around __construct() which allows more fluent coding.
6a488035 105 *
6a0b768e
TO
106 * @param bool $nest
107 * Determines what to do if there's currently an active transaction:.
5bb8417a
TO
108 * - If true, then make a new nested transaction ("SAVEPOINT")
109 * - If false, then attach to the existing transaction
110 * @return \CRM_Core_Transaction
6a488035 111 */
5bb8417a
TO
112 public static function create($nest = FALSE) {
113 return new self($nest);
114 }
a0ee3941
EM
115
116 /**
5bb8417a 117 * Ensure that an SQL transaction is started
a0ee3941 118 *
6a0b768e
TO
119 * @param bool $nest
120 * Determines what to do if there's currently an active transaction:.
5bb8417a
TO
121 * - If true, then make a new nested transaction ("SAVEPOINT")
122 * - If false, then attach to the existing transaction
a0ee3941 123 */
00be9182 124 public function __construct($nest = FALSE) {
5bb8417a 125 \Civi\Core\Transaction\Manager::singleton()->inc($nest);
6a488035
TO
126 }
127
00be9182 128 public function __destruct() {
6a488035
TO
129 $this->commit();
130 }
131
5bb8417a
TO
132 /**
133 * Immediately commit or rollback
134 *
135 * (Note: Prior to 4.6, return void)
136 *
137 * @return \CRM_Core_Exception this
138 */
00be9182 139 public function commit() {
5bb8417a 140 if (!$this->_pseudoCommitted) {
6a488035 141 $this->_pseudoCommitted = TRUE;
5bb8417a 142 \Civi\Core\Transaction\Manager::singleton()->dec();
6a488035 143 }
5bb8417a 144 return $this;
6a488035
TO
145 }
146
a0ee3941
EM
147 /**
148 * @param $flag
149 */
6a488035 150 static public function rollbackIfFalse($flag) {
5bb8417a
TO
151 $frame = \Civi\Core\Transaction\Manager::singleton()->getFrame();
152 if ($flag === FALSE && $frame !== NULL) {
153 $frame->setRollbackOnly();
6a488035
TO
154 }
155 }
156
5bb8417a
TO
157 /**
158 * Mark the transaction for rollback.
159 *
160 * (Note: Prior to 4.6, return void)
ba3228d1 161 * @return \CRM_Core_Transaction
5bb8417a 162 */
6a488035 163 public function rollback() {
5bb8417a
TO
164 $frame = \Civi\Core\Transaction\Manager::singleton()->getFrame();
165 if ($frame !== NULL) {
166 $frame->setRollbackOnly();
167 }
168 return $this;
169 }
170
171 /**
172 * Execute a function ($callable) within the scope of a transaction. If
173 * $callable encounters an unhandled exception, then rollback the transaction.
174 *
175 * After calling run(), the CRM_Core_Transaction object is "used up"; do not
176 * use it again.
177 *
6a0b768e
TO
178 * @param string $callable
179 * Should exception one paramter (CRM_Core_Transaction $tx).
ba3228d1 180 * @return CRM_Core_Transaction
5bb8417a
TO
181 * @throws Exception
182 */
183 public function run($callable) {
184 try {
185 $callable($this);
0db6c3e1
TO
186 }
187 catch (Exception $ex) {
5bb8417a
TO
188 $this->rollback()->commit();
189 throw $ex;
190 }
191 $this->commit();
192 return $this;
6a488035
TO
193 }
194
195 /**
196 * Force an immediate rollback, regardless of how many any
197 * CRM_Core_Transaction objects are waiting for
198 * pseudo-commits.
199 *
200 * Only rollback if the transaction API has been called.
201 *
202 * This is only appropriate when it is _certain_ that the
203 * callstack will not wind-down normally -- e.g. before
204 * a call to exit().
205 */
206 static public function forceRollbackIfEnabled() {
5bb8417a
TO
207 if (\Civi\Core\Transaction\Manager::singleton()->getFrame() !== NULL) {
208 \Civi\Core\Transaction\Manager::singleton()->forceRollback();
6a488035
TO
209 }
210 }
211
a0ee3941
EM
212 /**
213 * @return bool
214 */
6a488035 215 static public function willCommit() {
5bb8417a
TO
216 $frame = \Civi\Core\Transaction\Manager::singleton()->getFrame();
217 return ($frame === NULL) ? TRUE : !$frame->isRollbackOnly();
6a488035
TO
218 }
219
220 /**
221 * Determine whether there is a pending transaction
222 */
223 static public function isActive() {
5bb8417a
TO
224 $frame = \Civi\Core\Transaction\Manager::singleton()->getFrame();
225 return ($frame !== NULL);
6a488035
TO
226 }
227
228 /**
229 * Add a transaction callback
230 *
5bb8417a
TO
231 * Note: It's conceivable to add callbacks to the main/overall transaction
232 * (aka $manager->getBaseFrame()) or to the innermost nested transaction
233 * (aka $manager->getFrame()). addCallback() has been used in the past to
234 * work-around deadlocks. This may or may not be necessary now -- but it
235 * seems more consistent (for b/c purposes) to attach callbacks to the
236 * main/overall transaction.
237 *
6a488035
TO
238 * Pre-condition: isActive()
239 *
6a0b768e
TO
240 * @param int $phase
241 * A constant; one of: self::PHASE_{PRE,POST}_{COMMIT,ROLLBACK}.
242 * @param string $callback
243 * A PHP callback.
244 * @param mixed $params
245 * Optional values to pass to callback.
6a488035 246 * See php manual call_user_func_array for details.
ba3228d1 247 * @param int $id
6a488035 248 */
2aa397bc 249 static public function addCallback($phase, $callback, $params = NULL, $id = NULL) {
5bb8417a
TO
250 $frame = \Civi\Core\Transaction\Manager::singleton()->getBaseFrame();
251 $frame->addCallback($phase, $callback, $params, $id);
6a488035
TO
252 }
253}