No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

214 líneas
8.3KB

  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\db;
  8. use Yii;
  9. use yii\base\InvalidConfigException;
  10. /**
  11. * Transaction represents a DB transaction.
  12. *
  13. * It is usually created by calling [[Connection::beginTransaction()]].
  14. *
  15. * The following code is a typical example of using transactions (note that some
  16. * DBMS may not support transactions):
  17. *
  18. * ~~~
  19. * $transaction = $connection->beginTransaction();
  20. * try {
  21. * $connection->createCommand($sql1)->execute();
  22. * $connection->createCommand($sql2)->execute();
  23. * //.... other SQL executions
  24. * $transaction->commit();
  25. * } catch (Exception $e) {
  26. * $transaction->rollBack();
  27. * }
  28. * ~~~
  29. *
  30. * @property boolean $isActive Whether this transaction is active. Only an active transaction can [[commit()]]
  31. * or [[rollBack()]]. This property is read-only.
  32. * @property string $isolationLevel The transaction isolation level to use for this transaction. This can be
  33. * one of [[READ_UNCOMMITTED]], [[READ_COMMITTED]], [[REPEATABLE_READ]] and [[SERIALIZABLE]] but also a string
  34. * containing DBMS specific syntax to be used after `SET TRANSACTION ISOLATION LEVEL`. This property is
  35. * write-only.
  36. *
  37. * @author Qiang Xue <qiang.xue@gmail.com>
  38. * @since 2.0
  39. */
  40. class Transaction extends \yii\base\Object
  41. {
  42. /**
  43. * A constant representing the transaction isolation level `READ UNCOMMITTED`.
  44. * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
  45. */
  46. const READ_UNCOMMITTED = 'READ UNCOMMITTED';
  47. /**
  48. * A constant representing the transaction isolation level `READ COMMITTED`.
  49. * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
  50. */
  51. const READ_COMMITTED = 'READ COMMITTED';
  52. /**
  53. * A constant representing the transaction isolation level `REPEATABLE READ`.
  54. * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
  55. */
  56. const REPEATABLE_READ = 'REPEATABLE READ';
  57. /**
  58. * A constant representing the transaction isolation level `SERIALIZABLE`.
  59. * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
  60. */
  61. const SERIALIZABLE = 'SERIALIZABLE';
  62. /**
  63. * @var Connection the database connection that this transaction is associated with.
  64. */
  65. public $db;
  66. /**
  67. * @var integer the nesting level of the transaction. 0 means the outermost level.
  68. */
  69. private $_level = 0;
  70. /**
  71. * Returns a value indicating whether this transaction is active.
  72. * @return boolean whether this transaction is active. Only an active transaction
  73. * can [[commit()]] or [[rollBack()]].
  74. */
  75. public function getIsActive()
  76. {
  77. return $this->_level > 0 && $this->db && $this->db->isActive;
  78. }
  79. /**
  80. * Begins a transaction.
  81. * @param string|null $isolationLevel The [isolation level][] to use for this transaction.
  82. * This can be one of [[READ_UNCOMMITTED]], [[READ_COMMITTED]], [[REPEATABLE_READ]] and [[SERIALIZABLE]] but
  83. * also a string containing DBMS specific syntax to be used after `SET TRANSACTION ISOLATION LEVEL`.
  84. * If not specified (`null`) the isolation level will not be set explicitly and the DBMS default will be used.
  85. *
  86. * > Note: This setting does not work for PostgreSQL, where setting the isolation level before the transaction
  87. * has no effect. You have to call [[setIsolationLevel()]] in this case after the transaction has started.
  88. *
  89. * > Note: Some DBMS allow setting of the isolation level only for the whole connection so subsequent transactions
  90. * may get the same isolation level even if you did not specify any. When using this feature
  91. * you may need to set the isolation level for all transactions explicitly to avoid conflicting settings.
  92. * At the time of this writing affected DBMS are MSSQL and SQLite.
  93. *
  94. * [isolation level]: http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
  95. * @throws InvalidConfigException if [[db]] is `null`.
  96. */
  97. public function begin($isolationLevel = null)
  98. {
  99. if ($this->db === null) {
  100. throw new InvalidConfigException('Transaction::db must be set.');
  101. }
  102. $this->db->open();
  103. if ($this->_level == 0) {
  104. if ($isolationLevel !== null) {
  105. $this->db->getSchema()->setTransactionIsolationLevel($isolationLevel);
  106. }
  107. Yii::trace('Begin transaction' . ($isolationLevel ? ' with isolation level ' . $isolationLevel : ''), __METHOD__);
  108. $this->db->trigger(Connection::EVENT_BEGIN_TRANSACTION);
  109. $this->db->pdo->beginTransaction();
  110. $this->_level = 1;
  111. return;
  112. }
  113. $schema = $this->db->getSchema();
  114. if ($schema->supportsSavepoint()) {
  115. Yii::trace('Set savepoint ' . $this->_level, __METHOD__);
  116. $schema->createSavepoint('LEVEL' . $this->_level);
  117. } else {
  118. Yii::info('Transaction not started: nested transaction not supported', __METHOD__);
  119. }
  120. $this->_level++;
  121. }
  122. /**
  123. * Commits a transaction.
  124. * @throws Exception if the transaction is not active
  125. */
  126. public function commit()
  127. {
  128. if (!$this->getIsActive()) {
  129. throw new Exception('Failed to commit transaction: transaction was inactive.');
  130. }
  131. $this->_level--;
  132. if ($this->_level == 0) {
  133. Yii::trace('Commit transaction', __METHOD__);
  134. $this->db->pdo->commit();
  135. $this->db->trigger(Connection::EVENT_COMMIT_TRANSACTION);
  136. return;
  137. }
  138. $schema = $this->db->getSchema();
  139. if ($schema->supportsSavepoint()) {
  140. Yii::trace('Release savepoint ' . $this->_level, __METHOD__);
  141. $schema->releaseSavepoint('LEVEL' . $this->_level);
  142. } else {
  143. Yii::info('Transaction not committed: nested transaction not supported', __METHOD__);
  144. }
  145. }
  146. /**
  147. * Rolls back a transaction.
  148. * @throws Exception if the transaction is not active
  149. */
  150. public function rollBack()
  151. {
  152. if (!$this->getIsActive()) {
  153. // do nothing if transaction is not active: this could be the transaction is committed
  154. // but the event handler to "commitTransaction" throw an exception
  155. return;
  156. }
  157. $this->_level--;
  158. if ($this->_level == 0) {
  159. Yii::trace('Roll back transaction', __METHOD__);
  160. $this->db->pdo->rollBack();
  161. $this->db->trigger(Connection::EVENT_ROLLBACK_TRANSACTION);
  162. return;
  163. }
  164. $schema = $this->db->getSchema();
  165. if ($schema->supportsSavepoint()) {
  166. Yii::trace('Roll back to savepoint ' . $this->_level, __METHOD__);
  167. $schema->rollBackSavepoint('LEVEL' . $this->_level);
  168. } else {
  169. Yii::info('Transaction not rolled back: nested transaction not supported', __METHOD__);
  170. // throw an exception to fail the outer transaction
  171. throw new Exception('Roll back failed: nested transaction not supported.');
  172. }
  173. }
  174. /**
  175. * Sets the transaction isolation level for this transaction.
  176. *
  177. * This method can be used to set the isolation level while the transaction is already active.
  178. * However this is not supported by all DBMS so you might rather specify the isolation level directly
  179. * when calling [[begin()]].
  180. * @param string $level The transaction isolation level to use for this transaction.
  181. * This can be one of [[READ_UNCOMMITTED]], [[READ_COMMITTED]], [[REPEATABLE_READ]] and [[SERIALIZABLE]] but
  182. * also a string containing DBMS specific syntax to be used after `SET TRANSACTION ISOLATION LEVEL`.
  183. * @throws Exception if the transaction is not active
  184. * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
  185. */
  186. public function setIsolationLevel($level)
  187. {
  188. if (!$this->getIsActive()) {
  189. throw new Exception('Failed to set isolation level: transaction was inactive.');
  190. }
  191. Yii::trace('Setting transaction isolation level to ' . $level, __METHOD__);
  192. $this->db->getSchema()->setTransactionIsolationLevel($level);
  193. }
  194. }