From 89d51b244e16a7bd7194c48e8fd54d1ecdecadca Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 10 Mar 2015 16:49:11 -0400 Subject: [PATCH] ar guide WIP [skip ci] --- docs/guide/db-active-record.md | 208 +++++++++++++++++++++++------------------ 1 file changed, 119 insertions(+), 89 deletions(-) diff --git a/docs/guide/db-active-record.md b/docs/guide/db-active-record.md index 5875a5a..d979b32 100644 --- a/docs/guide/db-active-record.md +++ b/docs/guide/db-active-record.md @@ -422,13 +422,17 @@ $post->updateCounters(['view_count' => 1]); ### Dirty Attributes When you call [[yii\db\ActiveRecord::save()|save()]] to save an Active Record instance, only *dirty attributes* -are being saved. An attribute is considered *dirty* if its value has been modified since the Active Record instance -was newly created or fetched from database. Note that data validation will be performed regardless if the Active Record +are being saved. An attribute is considered *dirty* if its value has been modified since it was loaded from DB or +saved to DB most recently. Note that data validation will be performed regardless if the Active Record instance has dirty attributes or not. -Active Record automatically maintains the list of dirty attributes. You can call [[yii\db\ActiveRecord::getDirtyAttributes()]] -to get the dirty attribute values. You can also call [[yii\db\ActiveRecord::markAttributeDirty()]] to explicitly mark -an attribute as dirty. +Active Record automatically maintains the list of dirty attributes. It does so by maintaining an older version of +the attribute values and comparing them with the latest one. You can call [[yii\db\ActiveRecord::getDirtyAttributes()]] +to get the attributes that are currently dirty. You can also call [[yii\db\ActiveRecord::markAttributeDirty()]] +to explicitly mark an attribute as dirty. + +If you are interested in the attribute values prior to their most recent modification, you may call +[[yii\db\ActiveRecord::getOldAttributes()|getOldAttributes()]] or [[yii\db\ActiveRecord::getOldAttribute()|getOldAttribute()]]. ### Default Attribute Values @@ -441,7 +445,7 @@ into the corresponding Active Record attributes: ```php $customer = new Customer(); $customer->loadDefaultValues(); -// ... render HTML form for $customer ... +// $customer->xyz will be assigned the default value declared when defining the "xyz" column ``` @@ -485,39 +489,125 @@ Customer::deleteAll(['status' => Customer::STATUS_INACTIVE]); erase all data from your table if you make a mistake in specifying the condition. -## Active Record Life Cycles +## Active Record Life Cycles + +It is important to understand the life cycles of Active Record when it is used for different purposes. +During each life cycle, a certain sequence of methods will be invoked, and you can override these methods +to get a chance to customize the life cycle. You can also respond to certain Active Record events triggered +during a life cycle to inject your custom code. These events are especially useful when you are developing +Active Record [behaviors](concept-behaviors.md) which need to customize Active Record life cycles. + +In the following, we will summarize various Active Record life cycles and the methods/events that are involved +in the life cycles. + + +### New Instance Life Cycle + +When creating a new Active Record instance via the `new` operator, the following life cycle will happen: + +1. class constructor; +2. [[yii\db\ActiveRecord::init()|init()]]: triggers an [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]] event. -It is important to understand the life cycles of Active Record when it is used to manipulate data in database. -These life cycles are typically associated with corresponding events which allow you to inject code -to intercept or respond to these events. They are especially useful for developing Active Record [behaviors](concept-behaviors.md). -When instantiating a new Active Record instance, we will have the following life cycles: +### Querying Data Life Cycle -1. constructor -2. [[yii\db\ActiveRecord::init()|init()]]: will trigger an [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]] event +When querying data through one of the [querying methods](#querying-data), each newly populated Active Record will +undergo the following life cycle: -When querying data through the [[yii\db\ActiveRecord::find()|find()]] method, we will have the following life cycles -for EVERY newly populated Active Record instance: +1. class constructor. +2. [[yii\db\ActiveRecord::init()|init()]]: triggers an [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]] event. +3. [[yii\db\ActiveRecord::afterFind()|afterFind()]]: triggers an [[yii\db\ActiveRecord::EVENT_AFTER_FIND|EVENT_AFTER_FIND]] event. -1. constructor -2. [[yii\db\ActiveRecord::init()|init()]]: will trigger an [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]] event -3. [[yii\db\ActiveRecord::afterFind()|afterFind()]]: will trigger an [[yii\db\ActiveRecord::EVENT_AFTER_FIND|EVENT_AFTER_FIND]] event -When calling [[yii\db\ActiveRecord::save()|save()]] to insert or update an ActiveRecord, we will have -the following life cycles: +### Saving Data Life Cycle -1. [[yii\db\ActiveRecord::beforeValidate()|beforeValidate()]]: will trigger an [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] event -2. [[yii\db\ActiveRecord::afterValidate()|afterValidate()]]: will trigger an [[yii\db\ActiveRecord::EVENT_AFTER_VALIDATE|EVENT_AFTER_VALIDATE]] event -3. [[yii\db\ActiveRecord::beforeSave()|beforeSave()]]: will trigger an [[yii\db\ActiveRecord::EVENT_BEFORE_INSERT|EVENT_BEFORE_INSERT]] or [[yii\db\ActiveRecord::EVENT_BEFORE_UPDATE|EVENT_BEFORE_UPDATE]] event -4. perform the actual data insertion or updating -5. [[yii\db\ActiveRecord::afterSave()|afterSave()]]: will trigger an [[yii\db\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] or [[yii\db\ActiveRecord::EVENT_AFTER_UPDATE|EVENT_AFTER_UPDATE]] event +When calling [[yii\db\ActiveRecord::save()|save()]] to insert or update an Active Record instance, the following +life cycle will happen: -And finally, when calling [[yii\db\ActiveRecord::delete()|delete()]] to delete an ActiveRecord, we will have -the following life cycles: +1. [[yii\db\ActiveRecord::beforeValidate()|beforeValidate()]]: triggers + an [[yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE]] event. If the method returns false + or [[yii\base\ModelEvent::isValid]] is false, the rest of the steps will be skipped. +2. Performs data validation. If data validation fails, the steps after Step 3 will be skipped. +3. [[yii\db\ActiveRecord::afterValidate()|afterValidate()]]: triggers + an [[yii\db\ActiveRecord::EVENT_AFTER_VALIDATE|EVENT_AFTER_VALIDATE]] event. +4. [[yii\db\ActiveRecord::beforeSave()|beforeSave()]]: triggers + an [[yii\db\ActiveRecord::EVENT_BEFORE_INSERT|EVENT_BEFORE_INSERT]] + or [[yii\db\ActiveRecord::EVENT_BEFORE_UPDATE|EVENT_BEFORE_UPDATE]] event. If the method returns false + or [[yii\base\ModelEvent::isValid]] is false, the rest of the steps will be skipped. +5. Performs the actual data insertion or updating; +6. [[yii\db\ActiveRecord::afterSave()|afterSave()]]: triggers + an [[yii\db\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] + or [[yii\db\ActiveRecord::EVENT_AFTER_UPDATE|EVENT_AFTER_UPDATE]] event. + + +### Deleting Data Life Cycle -1. [[yii\db\ActiveRecord::beforeDelete()|beforeDelete()]]: will trigger an [[yii\db\ActiveRecord::EVENT_BEFORE_DELETE|EVENT_BEFORE_DELETE]] event +When calling [[yii\db\ActiveRecord::delete()|delete()]] to delete an Active Record instance, the following +life cycle will happen: + +1. [[yii\db\ActiveRecord::beforeDelete()|beforeDelete()]]: triggers + an [[yii\db\ActiveRecord::EVENT_BEFORE_DELETE|EVENT_BEFORE_DELETE]] event. If the method returns false + or [[yii\base\ModelEvent::isValid]] is false, the rest of the steps will be skipped. 2. perform the actual data deletion -3. [[yii\db\ActiveRecord::afterDelete()|afterDelete()]]: will trigger an [[yii\db\ActiveRecord::EVENT_AFTER_DELETE|EVENT_AFTER_DELETE]] event +3. [[yii\db\ActiveRecord::afterDelete()|afterDelete()]]: triggers + an [[yii\db\ActiveRecord::EVENT_AFTER_DELETE|EVENT_AFTER_DELETE]] event. + + +> Note: Calling any of the following methods will NOT initiate any of the above life cycles: +> +> - [[yii\db\ActiveRecord::updateAll()]] +> - [[yii\db\ActiveRecord::deleteAll()]] +> - [[yii\db\ActiveRecord::updateCounters()]] +> - [[yii\db\ActiveRecord::updateAllCounters()]] + + +## Transactional Operations + +There are two ways of dealing with transactions while working with Active Record. First way is doing everything manually +as described in the "transactions" section of "[Database basics](db-dao.md)". Another way is to implement the +`transactions` method where you can specify which operations are to be wrapped into transactions on a per model scenario: + +```php +class Post extends \yii\db\ActiveRecord +{ + public function transactions() + { + return [ + '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, + ]; + } +} +``` + +In the above `admin` and `api` are model scenarios and the constants starting with `OP_` are operations that should +be wrapped in transactions for these scenarios. Supported operations are `OP_INSERT`, `OP_UPDATE` and `OP_DELETE`. +`OP_ALL` stands for all three. + +Such automatic transactions are especially useful if you're doing additional database changes in `beforeSave`, +`afterSave`, `beforeDelete`, `afterDelete` and want to be sure that both succeeded before they are saved. + + +## Optimistic Locks + +Optimistic locking allows multiple users to access the same record for edits and avoids +potential conflicts. For example, 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. + +Optimistic locking is only supported by `update()` and `delete()` methods and isn't used by default. + +To use Optimistic locking: + +1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`. + Override the `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 record 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 staled data) + to resolve the conflict. ## Working with Relational Data @@ -1104,63 +1194,3 @@ Note that all your queries should then not use [[yii\db\ActiveQuery::where()|whe [[yii\db\ActiveQuery::andWhere()|andWhere()]] and [[yii\db\ActiveQuery::orWhere()|orWhere()]] to not override the default condition. - -Transactional operations ---------------------- - -There are two ways of dealing with transactions while working with Active Record. First way is doing everything manually -as described in the "transactions" section of "[Database basics](db-dao.md)". Another way is to implement the -`transactions` method where you can specify which operations are to be wrapped into transactions on a per model scenario: - -```php -class Post extends \yii\db\ActiveRecord -{ - public function transactions() - { - return [ - '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, - ]; - } -} -``` - -In the above `admin` and `api` are model scenarios and the constants starting with `OP_` are operations that should -be wrapped in transactions for these scenarios. Supported operations are `OP_INSERT`, `OP_UPDATE` and `OP_DELETE`. -`OP_ALL` stands for all three. - -Such automatic transactions are especially useful if you're doing additional database changes in `beforeSave`, -`afterSave`, `beforeDelete`, `afterDelete` and want to be sure that both succeeded before they are saved. - -Optimistic Locks --------------- - -Optimistic locking allows multiple users to access the same record for edits and avoids -potential conflicts. For example, 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. - -Optimistic locking is only supported by `update()` and `delete()` methods and isn't used by default. - -To use Optimistic locking: - -1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`. - Override the `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 record 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 staled data) - to resolve the conflict. - -Dirty Attributes --------------- - -An attribute is considered dirty if its value was modified after the model was loaded from database or since the most recent data save. When saving record data by calling `save()`, `update()`, `insert()` etc. only dirty attributes are saved into the database. If there are no dirty attributes then there is nothing to be saved so no query will be issued at all. - -See also --------- - -- [Model](structure-models.md) -- [[yii\db\ActiveRecord]]