Commit 4727ac8f by Qiang Xue

Refactored the feature of transactional operations.

parent db8233e5
...@@ -8,8 +8,11 @@ ...@@ -8,8 +8,11 @@
namespace yii\base; namespace yii\base;
use Yii; use Yii;
use ArrayAccess;
use ArrayObject; use ArrayObject;
use ArrayIterator; use ArrayIterator;
use ReflectionClass;
use IteratorAggregate;
use yii\helpers\Inflector; use yii\helpers\Inflector;
use yii\validators\RequiredValidator; use yii\validators\RequiredValidator;
use yii\validators\Validator; use yii\validators\Validator;
...@@ -42,7 +45,7 @@ use yii\validators\Validator; ...@@ -42,7 +45,7 @@ use yii\validators\Validator;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @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 * @event ModelEvent an event raised at the beginning of [[validate()]]. You may set
...@@ -184,7 +187,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess ...@@ -184,7 +187,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
*/ */
public function formName() public function formName()
{ {
$reflector = new \ReflectionClass($this); $reflector = new ReflectionClass($this);
return $reflector->getShortName(); return $reflector->getShortName();
} }
...@@ -196,7 +199,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess ...@@ -196,7 +199,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
*/ */
public function attributes() public function attributes()
{ {
$class = new \ReflectionClass($this); $class = new ReflectionClass($this);
$names = array(); $names = array();
foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
$name = $property->getName(); $name = $property->getName();
...@@ -608,9 +611,6 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess ...@@ -608,9 +611,6 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
return array(); return array();
} }
$attributes = array(); $attributes = array();
if (isset($scenarios[$scenario]['attributes']) && is_array($scenarios[$scenario]['attributes'])) {
$scenarios[$scenario] = $scenarios[$scenario]['attributes'];
}
foreach ($scenarios[$scenario] as $attribute) { foreach ($scenarios[$scenario] as $attribute) {
if ($attribute[0] !== '!') { if ($attribute[0] !== '!') {
$attributes[] = $attribute; $attributes[] = $attribute;
...@@ -630,11 +630,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess ...@@ -630,11 +630,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
if (!isset($scenarios[$scenario])) { if (!isset($scenarios[$scenario])) {
return array(); 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) { foreach ($attributes as $i => $attribute) {
if ($attribute[0] === '!') { if ($attribute[0] === '!') {
$attributes[$i] = substr($attribute, 1); $attributes[$i] = substr($attribute, 1);
......
...@@ -72,20 +72,22 @@ class ActiveRecord extends Model ...@@ -72,20 +72,22 @@ class ActiveRecord extends Model
const EVENT_AFTER_DELETE = 'afterDelete'; const EVENT_AFTER_DELETE = 'afterDelete';
/** /**
* Represents insert ActiveRecord operation. This constant is used for specifying set of atomic operations * The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
* for particular scenario in the [[scenarios()]] method.
*/ */
const OP_INSERT = 'insert'; const OP_INSERT = 0x01;
/** /**
* Represents update ActiveRecord operation. This constant is used for specifying set of atomic operations * The update operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
* for particular scenario in the [[scenarios()]] method.
*/ */
const OP_UPDATE = 'update'; const OP_UPDATE = 0x02;
/** /**
* Represents delete ActiveRecord operation. This constant is used for specifying set of atomic operations * The delete operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
* for particular scenario in the [[scenarios()]] method.
*/ */
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 * @var array attribute values indexed by attribute names
...@@ -331,6 +333,38 @@ class ActiveRecord extends Model ...@@ -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. * PHP getter magic method.
* This method is overridden so that attributes and related objects can be accessed like properties. * This method is overridden so that attributes and related objects can be accessed like properties.
* @param string $name property name * @param string $name property name
...@@ -712,7 +746,7 @@ class ActiveRecord extends Model ...@@ -712,7 +746,7 @@ class ActiveRecord extends Model
return false; return false;
} }
$db = static::getDb(); $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 { try {
$result = $this->insertInternal($attributes); $result = $this->insertInternal($attributes);
if ($transaction !== null) { if ($transaction !== null) {
...@@ -822,7 +856,7 @@ class ActiveRecord extends Model ...@@ -822,7 +856,7 @@ class ActiveRecord extends Model
return false; return false;
} }
$db = static::getDb(); $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 { try {
$result = $this->updateInternal($attributes); $result = $this->updateInternal($attributes);
if ($transaction !== null) { if ($transaction !== null) {
...@@ -929,7 +963,7 @@ class ActiveRecord extends Model ...@@ -929,7 +963,7 @@ class ActiveRecord extends Model
public function delete() public function delete()
{ {
$db = static::getDb(); $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 { try {
$result = false; $result = false;
if ($this->beforeDelete()) { if ($this->beforeDelete()) {
...@@ -1454,17 +1488,14 @@ class ActiveRecord extends Model ...@@ -1454,17 +1488,14 @@ class ActiveRecord extends Model
} }
/** /**
* @param string $operation possible values are ActiveRecord::INSERT, ActiveRecord::UPDATE and ActiveRecord::DELETE. * Returns a value indicating whether the specified operation is transactional in the current [[scenario]].
* @return boolean whether given operation is atomic. Currently active scenario is taken into account. * @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(); $scenario = $this->getScenario();
$scenarios = $this->scenarios(); $transactions = $this->transactions();
if (isset($scenarios[$scenario], $scenarios[$scenario]['atomic']) && is_array($scenarios[$scenario]['atomic'])) { return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation);
return in_array($operation, $scenarios[$scenario]['atomic']);
} else {
return false;
}
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment