Make "Chain Select" mechanism robust against missing 'target' form field
[civicrm-core.git] / CRM / Core / Transaction.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
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-2014
32 * @copyright David Strauss <david@fourkitchens.com> (c) 2007
33 * $Id$
34 *
35 * This file has its origins in Donald Lobo's conversation with David
36 * Strauss over IRC and the CRM_Core_DAO::transaction() function.
37 *
38 * David went on and abstracted this into a class which can be used in PHP 5
39 * (since destructors are called automagically at the end of the script).
40 * Lobo modified the code and used CiviCRM coding standards. David's
41 * PressFlow Transaction module is available at
42 * http://drupal.org/project/pressflow_transaction
43 */
44 class CRM_Core_Transaction {
45
46 /**
47 * These constants represent phases at which callbacks can be invoked
48 */
49 CONST PHASE_PRE_COMMIT = 1;
50 CONST PHASE_POST_COMMIT = 2;
51 CONST PHASE_PRE_ROLLBACK = 4;
52 CONST PHASE_POST_ROLLBACK = 8;
53
54 /**
55 * Keep track of the number of opens and close
56 *
57 * @var int
58 */
59 private static $_count = 0;
60
61 /**
62 * Keep track if we need to commit or rollback
63 *
64 * @var boolean
65 */
66 private static $_doCommit = TRUE;
67
68 /**
69 * hold a dao singleton for query operations
70 *
71 * @var object
72 */
73 private static $_dao = NULL;
74
75 /**
76 * Array of callbacks to invoke when the transaction commits or rolls back.
77 * Array keys are phase constants.
78 * Array values are arrays of callbacks.
79 */
80 private static $_callbacks = NULL;
81
82 /**
83 * Whether commit() has been called on this instance
84 * of CRM_Core_Transaction
85 */
86 private $_pseudoCommitted = FALSE;
87
88 /**
89 *
90 */
91 function __construct() {
92 if (!self::$_dao) {
93 self::$_dao = new CRM_Core_DAO();
94 }
95
96 if (self::$_count == 0) {
97 self::$_dao->query('BEGIN');
98 self::$_callbacks = array(
99 self::PHASE_PRE_COMMIT => array(),
100 self::PHASE_POST_COMMIT => array(),
101 self::PHASE_PRE_ROLLBACK => array(),
102 self::PHASE_POST_ROLLBACK => array(),
103 );
104 }
105
106 self::$_count++;
107 }
108
109 function __destruct() {
110 $this->commit();
111 }
112
113 function commit() {
114 if (self::$_count > 0 && !$this->_pseudoCommitted) {
115 $this->_pseudoCommitted = TRUE;
116 self::$_count--;
117
118 if (self::$_count == 0) {
119
120 // It's possible that, say, a POST_COMMIT callback creates another
121 // transaction. That transaction will need its own list of callbacks.
122 $oldCallbacks = self::$_callbacks;
123 self::$_callbacks = NULL;
124
125 if (self::$_doCommit) {
126 self::invokeCallbacks(self::PHASE_PRE_COMMIT, $oldCallbacks);
127 self::$_dao->query('COMMIT');
128 self::invokeCallbacks(self::PHASE_POST_COMMIT, $oldCallbacks);
129 }
130 else {
131 self::invokeCallbacks(self::PHASE_PRE_ROLLBACK, $oldCallbacks);
132 self::$_dao->query('ROLLBACK');
133 self::invokeCallbacks(self::PHASE_POST_ROLLBACK, $oldCallbacks);
134 }
135 // this transaction is complete, so reset doCommit flag
136 self::$_doCommit = TRUE;
137 }
138 }
139 }
140
141 /**
142 * @param $flag
143 */
144 static public function rollbackIfFalse($flag) {
145 if ($flag === FALSE) {
146 self::$_doCommit = FALSE;
147 }
148 }
149
150 public function rollback() {
151 self::$_doCommit = FALSE;
152 }
153
154 /**
155 * Force an immediate rollback, regardless of how many any
156 * CRM_Core_Transaction objects are waiting for
157 * pseudo-commits.
158 *
159 * Only rollback if the transaction API has been called.
160 *
161 * This is only appropriate when it is _certain_ that the
162 * callstack will not wind-down normally -- e.g. before
163 * a call to exit().
164 */
165 static public function forceRollbackIfEnabled() {
166 if (self::$_count > 0) {
167 $oldCallbacks = self::$_callbacks;
168 self::$_callbacks = NULL;
169 self::invokeCallbacks(self::PHASE_PRE_ROLLBACK, $oldCallbacks);
170 self::$_dao->query('ROLLBACK');
171 self::invokeCallbacks(self::PHASE_POST_ROLLBACK, $oldCallbacks);
172 self::$_count = 0;
173 self::$_doCommit = TRUE;
174 }
175 }
176
177 /**
178 * @return bool
179 */
180 static public function willCommit() {
181 return self::$_doCommit;
182 }
183
184 /**
185 * Determine whether there is a pending transaction
186 */
187 static public function isActive() {
188 return (self::$_count > 0);
189 }
190
191 /**
192 * Add a transaction callback
193 *
194 * Pre-condition: isActive()
195 *
196 * @param $phase A constant; one of: self::PHASE_{PRE,POST}_{COMMIT,ROLLBACK}
197 * @param $callback A PHP callback
198 * @param mixed $params Optional values to pass to callback.
199 * See php manual call_user_func_array for details.
200 */
201 static public function addCallback($phase, $callback, $params = null, $id = NULL) {
202 if ($id) {
203 self::$_callbacks[$phase][$id] = array(
204 'callback' => $callback,
205 'parameters' => (is_array($params) ? $params : array($params))
206 );
207 } else {
208 self::$_callbacks[$phase][] = array(
209 'callback' => $callback,
210 'parameters' => (is_array($params) ? $params : array($params))
211 );
212 }
213 }
214
215 /**
216 * @param $phase
217 * @param $callbacks
218 */
219 static protected function invokeCallbacks($phase, $callbacks) {
220 if (is_array($callbacks[$phase])) {
221 foreach ($callbacks[$phase] as $cb) {
222 call_user_func_array($cb['callback'], $cb['parameters']);
223 }
224 }
225 }
226 }
227