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 * @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); } }