From 4727ac8f1d61a538f9cafa0c2350f8a5618e8ff3 Mon Sep 17 00:00:00 2001
From: Qiang Xue <qiang.xue@gmail.com>
Date: Wed, 7 Aug 2013 22:22:23 -0400
Subject: [PATCH] Refactored the feature of transactional operations.

---
 framework/yii/base/Model.php      | 18 +++++++-----------
 framework/yii/db/ActiveRecord.php | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
 2 files changed, 59 insertions(+), 32 deletions(-)

diff --git a/framework/yii/base/Model.php b/framework/yii/base/Model.php
index bb0f4b1..f1f1072 100644
--- a/framework/yii/base/Model.php
+++ b/framework/yii/base/Model.php
@@ -8,8 +8,11 @@
 namespace yii\base;
 
 use Yii;
+use ArrayAccess;
 use ArrayObject;
 use ArrayIterator;
+use ReflectionClass;
+use IteratorAggregate;
 use yii\helpers\Inflector;
 use yii\validators\RequiredValidator;
 use yii\validators\Validator;
@@ -42,7 +45,7 @@ use yii\validators\Validator;
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class Model extends Component implements \IteratorAggregate, \ArrayAccess
+class Model extends Component implements IteratorAggregate, ArrayAccess
 {
 	/**
 	 * @event ModelEvent an event raised at the beginning of [[validate()]]. You may set
@@ -184,7 +187,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
 	 */
 	public function formName()
 	{
-		$reflector = new \ReflectionClass($this);
+		$reflector = new ReflectionClass($this);
 		return $reflector->getShortName();
 	}
 
@@ -196,7 +199,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
 	 */
 	public function attributes()
 	{
-		$class = new \ReflectionClass($this);
+		$class = new ReflectionClass($this);
 		$names = array();
 		foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
 			$name = $property->getName();
@@ -608,9 +611,6 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
 			return array();
 		}
 		$attributes = array();
-		if (isset($scenarios[$scenario]['attributes']) && is_array($scenarios[$scenario]['attributes'])) {
-			$scenarios[$scenario] = $scenarios[$scenario]['attributes'];
-		}
 		foreach ($scenarios[$scenario] as $attribute) {
 			if ($attribute[0] !== '!') {
 				$attributes[] = $attribute;
@@ -630,11 +630,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
 		if (!isset($scenarios[$scenario])) {
 			return array();
 		}
-		if (isset($scenarios[$scenario]['attributes']) && is_array($scenarios[$scenario]['attributes'])) {
-			$attributes = $scenarios[$scenario]['attributes'];
-		} else {
-			$attributes = $scenarios[$scenario];
-		}
+		$attributes = $scenarios[$scenario];
 		foreach ($attributes as $i => $attribute) {
 			if ($attribute[0] === '!') {
 				$attributes[$i] = substr($attribute, 1);
diff --git a/framework/yii/db/ActiveRecord.php b/framework/yii/db/ActiveRecord.php
index 6e42106..d385bed 100644
--- a/framework/yii/db/ActiveRecord.php
+++ b/framework/yii/db/ActiveRecord.php
@@ -72,20 +72,22 @@ class ActiveRecord extends Model
 	const EVENT_AFTER_DELETE = 'afterDelete';
 
 	/**
-	 * Represents insert ActiveRecord operation. This constant is used for specifying set of atomic operations
-	 * for particular scenario in the [[scenarios()]] method.
+	 * The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
 	 */
-	const OP_INSERT = 'insert';
+	const OP_INSERT = 0x01;
 	/**
-	 * Represents update ActiveRecord operation. This constant is used for specifying set of atomic operations
-	 * for particular scenario in the [[scenarios()]] method.
+	 * The update operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
 	 */
-	const OP_UPDATE = 'update';
+	const OP_UPDATE = 0x02;
 	/**
-	 * Represents delete ActiveRecord operation. This constant is used for specifying set of atomic operations
-	 * for particular scenario in the [[scenarios()]] method.
+	 * The delete operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
 	 */
-	const OP_DELETE = 'delete';
+	const OP_DELETE = 0x04;
+	/**
+	 * All three operations: insert, update, delete.
+	 * This is a shortcut of the expression: OP_INSERT | OP_UPDATE | OP_DELETE.
+	 */
+	const OP_ALL = 0x07;
 
 	/**
 	 * @var array attribute values indexed by attribute names
@@ -331,6 +333,38 @@ class ActiveRecord extends Model
 	}
 
 	/**
+	 * Declares which DB operations should be performed within a transaction in different scenarios.
+	 * The supported DB operations are: [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]],
+	 * which correspond to the [[insert()]], [[update()]] and [[delete()]] methods, respectively.
+	 * By default, these methods are NOT enclosed in a DB transaction.
+	 *
+	 * In some scenarios, to ensure data consistency, you may want to enclose some or all of them
+	 * in transactions. You can do so by overriding this method and returning the operations
+	 * that need to be transactional. For example,
+	 *
+	 * ~~~
+	 * return array(
+	 *     'admin' => self::OP_INSERT,
+	 *     'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
+	 *     // the above is equivalent to the following:
+	 *     // 'api' => self::OP_ALL,
+	 *
+	 * );
+	 * ~~~
+	 *
+	 * The above declaration specifies that in the "admin" scenario, the insert operation ([[insert()]])
+	 * should be done in a transaction; and in the "api" scenario, all the operations should be done
+	 * in a transaction.
+	 *
+	 * @return array the declarations of transactional operations. The array keys are scenarios names,
+	 * and the array values are the corresponding transaction operations.
+	 */
+	public function transactions()
+	{
+		return array();
+	}
+
+	/**
 	 * PHP getter magic method.
 	 * This method is overridden so that attributes and related objects can be accessed like properties.
 	 * @param string $name property name
@@ -712,7 +746,7 @@ class ActiveRecord extends Model
 			return false;
 		}
 		$db = static::getDb();
-		$transaction = $this->isOperationAtomic(self::OP_INSERT) && $db->getTransaction() === null ? $db->beginTransaction() : null;
+		$transaction = $this->isTransactional(self::OP_INSERT) && $db->getTransaction() === null ? $db->beginTransaction() : null;
 		try {
 			$result = $this->insertInternal($attributes);
 			if ($transaction !== null) {
@@ -822,7 +856,7 @@ class ActiveRecord extends Model
 			return false;
 		}
 		$db = static::getDb();
-		$transaction = $this->isOperationAtomic(self::OP_UPDATE) && $db->getTransaction() === null ? $db->beginTransaction() : null;
+		$transaction = $this->isTransactional(self::OP_UPDATE) && $db->getTransaction() === null ? $db->beginTransaction() : null;
 		try {
 			$result = $this->updateInternal($attributes);
 			if ($transaction !== null) {
@@ -929,7 +963,7 @@ class ActiveRecord extends Model
 	public function delete()
 	{
 		$db = static::getDb();
-		$transaction = $this->isOperationAtomic(self::OP_DELETE) && $db->getTransaction() === null ? $db->beginTransaction() : null;
+		$transaction = $this->isTransactional(self::OP_DELETE) && $db->getTransaction() === null ? $db->beginTransaction() : null;
 		try {
 			$result = false;
 			if ($this->beforeDelete()) {
@@ -1454,17 +1488,14 @@ class ActiveRecord extends Model
 	}
 
 	/**
-	 * @param string $operation possible values are ActiveRecord::INSERT, ActiveRecord::UPDATE and ActiveRecord::DELETE.
-	 * @return boolean whether given operation is atomic. Currently active scenario is taken into account.
+	 * Returns a value indicating whether the specified operation is transactional in the current [[scenario]].
+	 * @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]].
+	 * @return boolean whether the specified operation is transactional in the current [[scenario]].
 	 */
-	private function isOperationAtomic($operation)
+	public function isTransactional($operation)
 	{
 		$scenario = $this->getScenario();
-		$scenarios = $this->scenarios();
-		if (isset($scenarios[$scenario], $scenarios[$scenario]['atomic']) && is_array($scenarios[$scenario]['atomic'])) {
-			return in_array($operation, $scenarios[$scenario]['atomic']);
-		} else {
-			return false;
-		}
+		$transactions = $this->transactions();
+		return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation);
 	}
 }
--
libgit2 0.27.1