Commit 4727ac8f by Qiang Xue

Refactored the feature of transactional operations.

parent db8233e5
......@@ -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);
......
......@@ -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);
}
}
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