Commit dd61be67 by Alexander Makarov

Fixes #3248: described AR transactions, locks, dirty attributes

parent 24f46c64
...@@ -960,146 +960,56 @@ to not override the default condition. ...@@ -960,146 +960,56 @@ to not override the default condition.
Transactional operations Transactional operations
------------------------ ---------------------
When a few DB operations are related and are executed
TODO: FIXME: WIP, TBD, https://github.com/yiisoft/yii2/issues/226
,
[[yii\db\ActiveRecord::afterSave()|afterSave()]], [[yii\db\ActiveRecord::beforeDelete()|beforeDelete()]] and/or [[yii\db\ActiveRecord::afterDelete()|afterDelete()]] life cycle methods. Developer may come
to the solution of overriding ActiveRecord [[yii\db\ActiveRecord::save()|save()]] method with database transaction wrapping or
even using transaction in controller action, which is strictly speaking doesn't seem to be a good
practice (recall "skinny-controller / fat-model" fundamental rule).
Here these ways are (**DO NOT** use them unless you're sure what you are actually doing). Models:
```php
class Feature extends \yii\db\ActiveRecord
{
// ...
public function getProduct()
{
return $this->hasOne(Product::className(), ['id' => 'product_id']);
}
}
class Product extends \yii\db\ActiveRecord
{
// ...
public function getFeatures()
{
return $this->hasMany(Feature::className(), ['product_id' => 'id']);
}
}
```
Overriding [[yii\db\ActiveRecord::save()|save()]] method:
```php
class ProductController extends \yii\web\Controller
{
public function actionCreate()
{
// FIXME: TODO: WIP, TBD
}
}
```
Using transactions within controller layer: There are two ways of dealing with transactions while working with Active Record. First way is doing everything manually
as described in "transactions" section of "[Database basics](db-dao.md)". Another way is to do it by implementing
`transactions` method where you can specify which operations are to be wrapped into transaction per model scenario:
```php ```php
class ProductController extends \yii\web\Controller class Post extends \yii\db\ActiveRecord
{ {
public function actionCreate() return [
{ 'admin' => self::OP_INSERT,
// FIXME: TODO: WIP, TBD 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
} // the above is equivalent to the following:
} // 'api' => self::OP_ALL,
];
``` ```
Instead of using these fragile methods you should consider using atomic scenarios and operations feature. In the above `admin` and `api` are model scenarios and constants starting with `OP_` are operations that should
be wrapped in transaction for these sceanarios. Supported operations are `OP_INSERT`, `OP_UPDATE` and `OP_DELETE`.
`OP_ALL` stands for all three.
```php Such automatic transactions are especially useful if you're doing additional database changes in `beforeSave`,
class Feature extends \yii\db\ActiveRecord `afterSave`, `beforeDelete`, `afterDelete` and want to be sure that both succeeded before they are saved.
{
// ...
public function getProduct()
{
return $this->hasOne(Product::className(), ['product_id' => 'id']);
}
public function scenarios()
{
return [
'userCreates' => [
'attributes' => ['name', 'value'],
'atomic' => [self::OP_INSERT],
],
];
}
}
class Product extends \yii\db\ActiveRecord
{
// ...
public function getFeatures()
{
return $this->hasMany(Feature::className(), ['id' => 'product_id']);
}
public function scenarios()
{
return [
'userCreates' => [
'attributes' => ['title', 'price'],
'atomic' => [self::OP_INSERT],
],
];
}
public function afterValidate()
{
parent::afterValidate();
// FIXME: TODO: WIP, TBD
}
public function afterSave($insert) Optimistic Locks
{ --------------
parent::afterSave($insert);
if ($this->getScenario() === 'userCreates') {
// FIXME: TODO: WIP, TBD
}
}
}
```
Controller is very thin and neat: Optimistic locking allows multiple users to access the same record for edits and avoids
potential conflicts. In case when a user attempts to save the record upon some staled data
(because another user has modified the data), a [[\yii\db\StaleObjectException]] exception will be thrown,
and the update or deletion is skipped.
```php Optimistic locking is only supported by `update()` and `delete()` methods and isn't used by default.
class ProductController extends \yii\web\Controller
{
public function actionCreate()
{
// FIXME: TODO: WIP, TBD
}
}
```
Optimistic Locks To use Optimistic locking:
----------------
TODO 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`.
Override `optimisticLock()` method to return the name of this column.
2. In the Web form that collects the user input, add a hidden field that stores
the lock version of the recording being updated.
3. In the controller action that does the data updating, try to catch the [[\yii\db\StaleObjectException]]
and implement necessary business logic (e.g. merging the changes, prompting stated data)
to resolve the conflict.
Dirty Attributes Dirty Attributes
---------------- --------------
TODO An attribute is considered dirty if its value was modified since model was loaded from database or since most recent
data save. When saving record data by calling `save()`, `update()`, `insert()` etc. only dirty attributes are saved
into database. If there are no dirty attributes there's nothing to be saved so no query will be issued at all.
See also See also
-------- --------
......
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