diff --git a/docs/api/db/ActiveRecord.md b/docs/api/db/ActiveRecord.md index b6e6041..cf4e0b1 100644 --- a/docs/api/db/ActiveRecord.md +++ b/docs/api/db/ActiveRecord.md @@ -363,4 +363,86 @@ value of `$customer` and then call [[save()]] to save the order into database. ### Data Input and Validation -// todo \ No newline at end of file +TBD + + +### Life Cycles of an ActiveRecord Object + +An ActiveRecord object undergoes different life cycles when it is used in different cases. +Subclasses or ActiveRecord behaviors may "inject" custom code in these life cycles through +method overriding and event handling mechanisms. + +When instantiating a new ActiveRecord instance, we will have the following life cycles: + +1. constructor +2. [[init()]]: will trigger an [[init]] event + +When getting an ActiveRecord instance through the [[find()]] method, we will have the following life cycles: + +1. constructor +2. [[init()]]: will trigger an [[init]] event +3. [[afterFind()]]: will trigger an [[afterFind]] event + +When calling [[save()]] to insert or update an ActiveRecord, we will have the following life cycles: + +1. [[beforeValidate()]]: will trigger an [[beforeValidate]] event +2. [[beforeSave()]]: will trigger an [[beforeSave]] event +3. perform the actual data insertion or updating +4. [[afterSave()]]: will trigger an [[afterSave]] event +5. [[afterValidate()]]: will trigger an [[afterValidate]] event + +Finally when calling [[delete()]] to delete an ActiveRecord, we will have the following life cycles: + +1. [[beforeDelete()]]: will trigger an [[beforeDelete]] event +2. perform the actual data deletion +3. [[afterDelete()]]: will trigger an [[afterDelete]] event + + +### Scopes + +A scope is a method that customizes a given [[ActiveQuery]] object. Scope methods are defined +in the ActiveRecord classes. They can be invoked through the [[ActiveQuery]] object that is created +via [[find()]] or [[findBySql()]]. The following is an example: + +~~~ +class Customer extends \yii\db\ActiveRecord +{ + // ... + + /** + * @param ActiveQuery $query + */ + public function active($query) + { + $query->andWhere('status = 1'); + } +} + +$customers = Customer::find()->active()->all(); +~~~ + +In the above, the `active()` method is defined in `Customer` while we are calling it +through `ActiveQuery` returned by `Customer::find()`. + +Scopes can be parameterized. For example, we can define and use the following `olderThan` scope: + +~~~ +class Customer extends \yii\db\ActiveRecord +{ + // ... + + /** + * @param ActiveQuery $query + * @param integer $age + */ + public function olderThan($query, $age = 30) + { + $query->andWhere('age > :age', array(':age' => $age)); + } +} + +$customers = Customer::find()->olderThan(50)->all(); +~~~ + +The parameters should follow after the `$query` parameter when defining the scope method, and they +can take default values like shown above. diff --git a/framework/base/Object.php b/framework/base/Object.php index ddfe36b..a3425dc 100644 --- a/framework/base/Object.php +++ b/framework/base/Object.php @@ -78,7 +78,8 @@ class Object * will be implicitly called when executing `$object->property = $value;`. * @param string $name the property name or the event name * @param mixed $value the property value - * @throws UnknownPropertyException if the property is not defined or read-only. + * @throws UnknownPropertyException if the property is not defined + * @throws InvalidCallException if the property is read-only. * @see __get */ public function __set($name, $value) @@ -122,7 +123,7 @@ class Object * Note that if the property is not defined, this method will do nothing. * If the property is read-only, it will throw an exception. * @param string $name the property name - * @throws UnknownPropertyException if the property is read only. + * @throws InvalidCallException if the property is read only. */ public function __unset($name) { diff --git a/framework/db/ActiveQuery.php b/framework/db/ActiveQuery.php index dc857ef..df81a07 100644 --- a/framework/db/ActiveQuery.php +++ b/framework/db/ActiveQuery.php @@ -14,7 +14,6 @@ use yii\db\Connection; use yii\db\Command; use yii\db\QueryBuilder; use yii\db\Expression; -use yii\db\Exception; /** * ActiveQuery represents a DB query associated with an Active Record class. diff --git a/framework/db/ActiveRecord.php b/framework/db/ActiveRecord.php index 4647067..a1b9313 100644 --- a/framework/db/ActiveRecord.php +++ b/framework/db/ActiveRecord.php @@ -32,6 +32,8 @@ use yii\util\StringHelper; * @property mixed $primaryKey the primary key value. * @property mixed $oldPrimaryKey the old primary key value. * + * @event ModelEvent init an event that is triggered when the record is initialized (in [[init()]]). + * @event ModelEvent afterFind an event that is triggered after the record is created and populated with query result. * @event ModelEvent beforeInsert an event that is triggered before inserting a record. * You may set [[ModelEvent::isValid]] to be false to stop the insertion. * @event Event afterInsert an event that is triggered before inserting a record. @@ -762,6 +764,30 @@ class ActiveRecord extends Model } /** + * Initializes the object. + * This method is called at the end of the constructor. + * The default implementation will trigger an [[afterInsert]] event. + * If you override this method, make sure you call the parent implementation at the end + * to ensure triggering of the event. + */ + public function init() + { + parent::init(); + $this->trigger('init'); + } + + /** + * This method is called when the AR object is created and populated with the query result. + * The default implementation will trigger an [[afterFind]] event. + * When overriding this method, make sure you call the parent implementation to ensure the + * event is triggered. + */ + public function afterFind() + { + $this->trigger('afterFind'); + } + + /** * Sets the value indicating whether the record is new. * @param boolean $value whether the record is new and should be inserted when calling [[save()]]. * @see getIsNewRecord @@ -956,6 +982,7 @@ class ActiveRecord extends Model } } $record->_oldAttributes = $record->_attributes; + $record->afterFind(); return $record; } diff --git a/framework/db/Schema.php b/framework/db/Schema.php index bb4352a..ff7cc72 100644 --- a/framework/db/Schema.php +++ b/framework/db/Schema.php @@ -329,10 +329,10 @@ abstract class Schema extends \yii\base\Object /** * Extracts the PHP type from abstract DB type. - * @param string $type abstract DB type + * @param ColumnSchema $column the column schema information * @return string PHP type name */ - protected function getColumnPhpType($type) + protected function getColumnPhpType($column) { static $typeMap = array( // abstract type => php type 'smallint' => 'integer', @@ -341,13 +341,13 @@ abstract class Schema extends \yii\base\Object 'boolean' => 'boolean', 'float' => 'double', ); - if (isset($typeMap[$type])) { - if ($type === 'bigint') { - return PHP_INT_SIZE == 8 && !$this->unsigned ? 'integer' : 'string'; - } elseif ($type === 'integer') { - return PHP_INT_SIZE == 4 && $this->unsigned ? 'string' : 'integer'; + if (isset($typeMap[$column->type])) { + if ($column->type === 'bigint') { + return PHP_INT_SIZE == 8 && !$column->unsigned ? 'integer' : 'string'; + } elseif ($column->type === 'integer') { + return PHP_INT_SIZE == 4 && $column->unsigned ? 'string' : 'integer'; } else { - return $typeMap[$this->type]; + return $typeMap[$column->type]; } } else { return 'string'; diff --git a/framework/db/mysql/Schema.php b/framework/db/mysql/Schema.php index 82e02f0..f7826ed 100644 --- a/framework/db/mysql/Schema.php +++ b/framework/db/mysql/Schema.php @@ -165,7 +165,7 @@ class Schema extends \yii\db\Schema } } - $column->phpType = $this->getColumnPhpType($column->type); + $column->phpType = $this->getColumnPhpType($column); if ($column->type !== 'timestamp' || $info['Default'] !== 'CURRENT_TIMESTAMP') { $column->defaultValue = $column->typecast($info['Default']);