ActiveRecord.php 38.7 KB
Newer Older
w  
Qiang Xue committed
1 2
<?php
/**
w  
Qiang Xue committed
3
 * ActiveRecord class file.
w  
Qiang Xue committed
4 5 6
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @link http://www.yiiframework.com/
Qiang Xue committed
7
 * @copyright Copyright &copy; 2008-2012 Yii Software LLC
w  
Qiang Xue committed
8 9 10
 * @license http://www.yiiframework.com/license/
 */

w  
Qiang Xue committed
11 12
namespace yii\db\ar;

Qiang Xue committed
13 14 15
use yii\base\Model;
use yii\base\Event;
use yii\base\ModelEvent;
Qiang Xue committed
16 17 18 19
use yii\db\Exception;
use yii\db\dao\Connection;
use yii\db\dao\TableSchema;
use yii\db\dao\Query;
Qiang Xue committed
20 21
use yii\db\dao\Expression;
use yii\util\Text;
w  
Qiang Xue committed
22

w  
Qiang Xue committed
23
/**
w  
Qiang Xue committed
24
 * ActiveRecord is the base class for classes representing relational data.
w  
Qiang Xue committed
25 26
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
Alexander Makarov committed
27
 * @since 2.0
w  
Qiang Xue committed
28 29 30
 *
 * @property array $attributes
 */
Qiang Xue committed
31
abstract class ActiveRecord extends Model
w  
Qiang Xue committed
32 33
{
	/**
Qiang Xue committed
34 35 36 37 38
	 * @var array attribute values indexed by attribute names
	 */
	private $_attributes = array();
	/**
	 * @var array old attribute values indexed by attribute names.
w  
Qiang Xue committed
39
	 */
Qiang Xue committed
40
	private $_oldAttributes;
Qiang Xue committed
41 42 43 44
	/**
	 * @var array related records indexed by relation names.
	 */
	private $_related;
w  
Qiang Xue committed
45 46

	/**
Qiang Xue committed
47 48 49
	 * Returns the metadata for this AR class.
	 * @param boolean $refresh whether to rebuild the metadata.
	 * @return ActiveMetaData the meta for this AR class.
w  
Qiang Xue committed
50
	 */
Qiang Xue committed
51
	public static function getMetaData($refresh = false)
w  
Qiang Xue committed
52
	{
Qiang Xue committed
53
		return ActiveMetaData::getInstance(get_called_class(), $refresh);
w  
Qiang Xue committed
54 55
	}

Qiang Xue committed
56 57 58 59 60 61 62 63 64 65 66
	/**
	 * Returns the database connection used by this AR class.
	 * By default, the "db" application component is used as the database connection.
	 * You may override this method if you want to use a different database connection.
	 * @return Connection the database connection used by this AR class.
	 */
	public static function getDbConnection()
	{
		return \Yii::$application->getDb();
	}

Qiang Xue committed
67
	/**
Qiang Xue committed
68
	 * Creates an [[ActiveQuery]] instance for query purpose.
Qiang Xue committed
69
	 *
Qiang Xue committed
70 71
	 * Because [[ActiveQuery]] implements a set of query building methods,
	 * additional query conditions can be specified by calling the methods of [[ActiveQuery]].
Qiang Xue committed
72 73 74 75 76 77
	 *
	 * Below are some usage examples:
	 *
	 * ~~~
	 * // find all customers
	 * $customers = Customer::find()->all();
Qiang Xue committed
78
	 * // find a single customer whose primary key value is 10
Qiang Xue committed
79 80
	 * $customer = Customer::find(10)->one();
	 * // find all active customers and order them by their age:
Qiang Xue committed
81 82
	 * $customers = Customer::find()
	 *     ->where(array('status' => 1))
83
	 *     ->order('age')
Qiang Xue committed
84 85 86 87
	 *     ->all();
	 * // or alternatively:
	 * $customers = Customer::find(array(
	 *     'where' => array('status' => 1),
88
	 *     'order' => 'age',
Qiang Xue committed
89
	 * ))->all();
Qiang Xue committed
90 91 92 93
	 * ~~~
	 *
	 * @param mixed $q the query parameter. This can be one of the followings:
	 *
Qiang Xue committed
94 95
	 *  - a scalar value (integer or string): query by a single primary key value.
	 *  - an array of name-value pairs: it will be used to configure the [[ActiveQuery]] object.
Qiang Xue committed
96
	 *
Qiang Xue committed
97
	 * @return ActiveQuery the [[ActiveQuery]] instance for query purpose.
Qiang Xue committed
98 99 100
	 */
	public static function find($q = null)
	{
Qiang Xue committed
101 102 103 104 105
		$query = static::createActiveQuery();
		if (is_array($q)) {
			foreach ($q as $name => $value) {
				$query->$name = $value;
			}
Qiang Xue committed
106 107
		} elseif ($q !== null) {
			// query by primary key
Qiang Xue committed
108
			$primaryKey = static::getMetaData()->table->primaryKey;
Qiang Xue committed
109
			$query->where(array($primaryKey[0] => $q));
Qiang Xue committed
110
		}
Qiang Xue committed
111
		return $query;
w  
Qiang Xue committed
112 113
	}

Qiang Xue committed
114
	/**
Qiang Xue committed
115
	 * Creates an [[ActiveQuery]] instance and query by a given SQL statement.
Qiang Xue committed
116
	 * Note that because the SQL statement is already specified, calling further
117
	 * query methods (such as `where()`, `order()`) on [[ActiveQuery]] will have no effect.
Qiang Xue committed
118
	 * Methods such as `with()`, `asArray()` can still be called though.
Qiang Xue committed
119 120
	 * @param string $sql the SQL statement to be executed
	 * @param array $params parameters to be bound to the SQL statement during execution.
Qiang Xue committed
121
	 * @return ActiveQuery the [[ActiveQuery]] instance
Qiang Xue committed
122
	 */
Qiang Xue committed
123
	public static function findBySql($sql, $params = array())
w  
Qiang Xue committed
124
	{
Qiang Xue committed
125 126 127 128 129 130 131 132 133 134 135 136
		$query = static::createActiveQuery();
		$query->sql = $sql;
		return $query->params($params);
	}

	/**
	 * Performs a COUNT query for this AR class.
	 *
	 * Below are some usage examples:
	 *
	 * ~~~
	 * // count the total number of customers
Qiang Xue committed
137
	 * echo Customer::count()->value();
Qiang Xue committed
138 139 140
	 * // count the number of active customers:
	 * echo Customer::count(array(
	 *     'where' => array('status' => 1),
Qiang Xue committed
141 142 143 144 145 146 147
	 * ))->value();
	 * // equivalent usage:
	 * echo Customer::count()
	 *     ->where(array('status' => 1))
	 *     ->value();
	 * // customize the count option
	 * echo Customer::count('COUNT(DISTINCT age)')->value();
Qiang Xue committed
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
	 * ~~~
	 *
	 * @param mixed $q the query parameter. This can be one of the followings:
	 *
	 *  - a scalar value (integer or string): query by a single primary key value.
	 *  - an array of name-value pairs: it will be used to configure the [[ActiveQuery]] object for query purpose.
	 *
	 * @return integer the counting result
	 */
	public static function count($q = null)
	{
		$query = static::createActiveQuery();
		if (is_array($q)) {
			foreach ($q as $name => $value) {
				$query->$name = $value;
			}
		}
		if ($query->select === null) {
Qiang Xue committed
166
			$query->select = array('COUNT(*)');
Qiang Xue committed
167 168
		}
		return $query->value();
w  
Qiang Xue committed
169 170
	}

Qiang Xue committed
171 172 173 174 175 176 177 178
	/**
	 * Updates the whole table using the provided attribute values and conditions.
	 * @param array $attributes attribute values to be saved into the table
	 * @param string|array $condition the conditions that will be put in the WHERE part.
	 * Please refer to [[Query::where()]] on how to specify this parameter.
	 * @param array $params the parameters (name=>value) to be bound to the query.
	 * @return integer the number of rows updated
	 */
Qiang Xue committed
179
	public static function updateAll($attributes, $condition = '', $params = array())
w  
Qiang Xue committed
180
	{
Qiang Xue committed
181
		$query = new Query;
Qiang Xue committed
182 183
		$query->update(static::tableName(), $attributes, $condition, $params);
		return $query->createCommand(static::getDbConnection())->execute();
w  
Qiang Xue committed
184 185
	}

Qiang Xue committed
186 187 188 189 190 191 192 193 194
	/**
	 * Updates the whole table using the provided counter values and conditions.
	 * @param array $counters the counters to be updated (attribute name => increment value).
	 * @param string|array $condition the conditions that will be put in the WHERE part.
	 * Please refer to [[Query::where()]] on how to specify this parameter.
	 * @param array $params the parameters (name=>value) to be bound to the query.
	 * @return integer the number of rows updated
	 */
	public static function updateAllCounters($counters, $condition = '', $params = array())
w  
Qiang Xue committed
195
	{
Qiang Xue committed
196
		$db = static::getDbConnection();
Qiang Xue committed
197 198 199 200 201 202
		foreach ($counters as $name => $value) {
			$value = (int)$value;
			$quotedName = $db->quoteColumnName($name, true);
			$counters[$name] = new Expression($value >= 0 ? "$quotedName+$value" : "$quotedName$value");
		}
		$query = new Query;
Qiang Xue committed
203 204
		$query->update(static::tableName(), $counters, $condition, $params);
		return $query->createCommand($db)->execute();
w  
Qiang Xue committed
205 206
	}

Qiang Xue committed
207 208 209 210 211 212 213
	/**
	 * Deletes rows in the table using the provided conditions.
	 * @param string|array $condition the conditions that will be put in the WHERE part.
	 * Please refer to [[Query::where()]] on how to specify this parameter.
	 * @param array $params the parameters (name=>value) to be bound to the query.
	 * @return integer the number of rows updated
	 */
Qiang Xue committed
214
	public static function deleteAll($condition = '', $params = array())
w  
Qiang Xue committed
215
	{
Qiang Xue committed
216
		$query = new Query;
Qiang Xue committed
217 218
		$query->delete(static::tableName(), $condition, $params);
		return $query->createCommand(static::getDbConnection())->execute();
w  
Qiang Xue committed
219 220
	}

.  
Qiang Xue committed
221
	/**
Qiang Xue committed
222 223 224
	 * Creates a [[ActiveQuery]] instance.
	 * This method is called by [[find()]] and [[findBySql()]] to start a SELECT query.
	 * @return ActiveQuery the newly created [[ActiveQuery]] instance.
.  
Qiang Xue committed
225
	 */
Qiang Xue committed
226
	public static function createActiveQuery()
w  
Qiang Xue committed
227
	{
Qiang Xue committed
228
		return new ActiveQuery(get_called_class());
w  
Qiang Xue committed
229 230 231
	}

	/**
Qiang Xue committed
232 233 234
	 * Declares the name of the database table associated with this AR class.
	 * By default this method returns the class name as the table name by calling [[Text::camel2id()]].
	 * For example, 'Customer' becomes 'customer', and 'OrderDetail' becomes 'order_detail'.
w  
Qiang Xue committed
235 236 237
	 * You may override this method if the table is not named after this convention.
	 * @return string the table name
	 */
Qiang Xue committed
238
	public static function tableName()
w  
Qiang Xue committed
239
	{
Qiang Xue committed
240
		return Text::camel2id(basename(get_called_class()), '_');
w  
Qiang Xue committed
241 242 243
	}

	/**
Qiang Xue committed
244
	 * Declares the primary key name for this AR class.
Qiang Xue committed
245 246
	 * This method is meant to be overridden in case when the table has no primary key defined
	 * (for some legacy database). If the table already has a primary key,
w  
Qiang Xue committed
247
	 * you do not need to override this method. The default implementation simply returns null,
Qiang Xue committed
248 249
	 * meaning using the primary key defined in the database table.
	 * @return string|array the primary key of the associated database table.
w  
Qiang Xue committed
250 251 252 253
	 * If the key is a single column, it should return the column name;
	 * If the key is a composite one consisting of several columns, it should
	 * return the array of the key column names.
	 */
Qiang Xue committed
254
	public static function primaryKey()
w  
Qiang Xue committed
255 256 257 258
	{
	}

	/**
Qiang Xue committed
259
	 * Declares the relations for this AR class.
Qiang Xue committed
260
	 *
Qiang Xue committed
261
	 * Child classes may override this method to specify their relations.
Qiang Xue committed
262
	 *
Qiang Xue committed
263
	 * The following code shows how to declare relations for a `Programmer` AR class:
Qiang Xue committed
264 265 266
	 *
	 * ~~~
	 * return array(
Qiang Xue committed
267
	 *	 'manager:Manager' => '@.id = ?.manager_id',
Qiang Xue committed
268
	 *	 'assignments:Assignment[]' => array(
Qiang Xue committed
269
	 *		 'on' => '@.owner_id = ?.id AND @.status = 1',
270
	 *		 'order' => '@.create_time DESC',
Qiang Xue committed
271 272 273
	 *	 ),
	 *	 'projects:Project[]' => array(
	 *		 'via' => 'assignments',
Qiang Xue committed
274
	 *		 'on' => '@.id = ?.project_id',
Qiang Xue committed
275
	 *	 ),
Qiang Xue committed
276 277 278
	 * );
	 * ~~~
	 *
w  
Qiang Xue committed
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
	 * This method should be overridden to declare related objects.
	 *
	 * There are four types of relations that may exist between two active record objects:
	 * <ul>
	 * <li>BELONGS_TO: e.g. a member belongs to a team;</li>
	 * <li>HAS_ONE: e.g. a member has at most one profile;</li>
	 * <li>HAS_MANY: e.g. a team has many members;</li>
	 * <li>MANY_MANY: e.g. a member has many skills and a skill belongs to a member.</li>
	 * </ul>
	 *
	 * Besides the above relation types, a special relation called STAT is also supported
	 * that can be used to perform statistical query (or aggregational query).
	 * It retrieves the aggregational information about the related objects, such as the number
	 * of comments for each post, the average rating for each product, etc.
	 *
	 * Each kind of related objects is defined in this method as an array with the following elements:
	 * <pre>
	 * 'varName'=>array('relationType', 'className', 'foreign_key', ...additional options)
	 * </pre>
	 * where 'varName' refers to the name of the variable/property that the related object(s) can
	 * be accessed through; 'relationType' refers to the type of the relation, which can be one of the
	 * following four constants: self::BELONGS_TO, self::HAS_ONE, self::HAS_MANY and self::MANY_MANY;
	 * 'className' refers to the name of the active record class that the related object(s) is of;
	 * and 'foreign_key' states the foreign key that relates the two kinds of active record.
	 * Note, for composite foreign keys, they must be listed together, separated by commas;
	 * and for foreign keys used in MANY_MANY relation, the joining table must be declared as well
	 * (e.g. 'join_table(fk1, fk2)').
	 *
	 * Additional options may be specified as name-value pairs in the rest array elements:
	 * <ul>
	 * <li>'select': string|array, a list of columns to be selected. Defaults to '*', meaning all columns.
	 *   Column names should be disambiguated if they appear in an expression (e.g. COUNT(relationName.name) AS name_count).</li>
	 * <li>'condition': string, the WHERE clause. Defaults to empty. Note, column references need to
	 *   be disambiguated with prefix 'relationName.' (e.g. relationName.age&gt;20)</li>
	 * <li>'order': string, the ORDER BY clause. Defaults to empty. Note, column references need to
	 *   be disambiguated with prefix 'relationName.' (e.g. relationName.age DESC)</li>
	 * <li>'with': string|array, a list of child related objects that should be loaded together with this object.
	 *   Note, this is only honored by lazy loading, not eager loading.</li>
	 * <li>'joinType': type of join. Defaults to 'LEFT OUTER JOIN'.</li>
	 * <li>'alias': the alias for the table associated with this relationship.
	 *   This option has been available since version 1.0.1. It defaults to null,
	 *   meaning the table alias is the same as the relation name.</li>
Qiang Xue committed
321
	 * <li>'params': the parameters to be bound to the generated SQL statement.
w  
Qiang Xue committed
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
	 *   This should be given as an array of name-value pairs. This option has been
	 *   available since version 1.0.3.</li>
	 * <li>'on': the ON clause. The condition specified here will be appended
	 *   to the joining condition using the AND operator. This option has been
	 *   available since version 1.0.2.</li>
	 * <li>'index': the name of the column whose values should be used as keys
	 *   of the array that stores related objects. This option is only available to
	 *   HAS_MANY and MANY_MANY relations. This option has been available since version 1.0.7.</li>
	 * <li>'scopes': scopes to apply. In case of a single scope can be used like 'scopes'=>'scopeName',
	 *   in case of multiple scopes can be used like 'scopes'=>array('scopeName1','scopeName2').
	 *   This option has been available since version 1.1.9.</li>
	 * </ul>
	 *
	 * The following options are available for certain relations when lazy loading:
	 * <ul>
	 * <li>'group': string, the GROUP BY clause. Defaults to empty. Note, column references need to
	 *   be disambiguated with prefix 'relationName.' (e.g. relationName.age). This option only applies to HAS_MANY and MANY_MANY relations.</li>
	 * <li>'having': string, the HAVING clause. Defaults to empty. Note, column references need to
	 *   be disambiguated with prefix 'relationName.' (e.g. relationName.age). This option only applies to HAS_MANY and MANY_MANY relations.</li>
	 * <li>'limit': limit of the rows to be selected. This option does not apply to BELONGS_TO relation.</li>
	 * <li>'offset': offset of the rows to be selected. This option does not apply to BELONGS_TO relation.</li>
	 * <li>'through': name of the model's relation that will be used as a bridge when getting related data. Can be set only for HAS_ONE and HAS_MANY. This option has been available since version 1.1.7.</li>
	 * </ul>
	 *
	 * Below is an example declaring related objects for 'Post' active record class:
	 * <pre>
	 * return array(
Qiang Xue committed
349 350 351
	 *	 'author'=>array(self::BELONGS_TO, 'User', 'author_id'),
	 *	 'comments'=>array(self::HAS_MANY, 'Comment', 'post_id', 'with'=>'author', 'order'=>'create_time DESC'),
	 *	 'tags'=>array(self::MANY_MANY, 'Tag', 'post_tag(post_id, tag_id)', 'order'=>'name'),
w  
Qiang Xue committed
352 353 354 355 356
	 * );
	 * </pre>
	 *
	 * @return array list of related object declarations. Defaults to empty array.
	 */
Qiang Xue committed
357
	public static function relations()
w  
Qiang Xue committed
358 359 360 361
	{
		return array();
	}

Qiang Xue committed
362 363 364 365 366
	/**
	 * Returns the default named scope that should be implicitly applied to all queries for this model.
	 * Note, default scope only applies to SELECT queries. It is ignored for INSERT, UPDATE and DELETE queries.
	 * The default implementation simply returns an empty array. You may override this method
	 * if the model needs to be queried with some default criteria (e.g. only active records should be returned).
Qiang Xue committed
367 368
	 * @param BaseActiveQuery
	 * @return BaseActiveQuery the query criteria. This will be used as the parameter to the constructor
Qiang Xue committed
369 370
	 * of {@link CDbCriteria}.
	 */
Qiang Xue committed
371
	public static function defaultScope($query)
Qiang Xue committed
372
	{
Qiang Xue committed
373
		return $query;
Qiang Xue committed
374 375
	}

w  
Qiang Xue committed
376 377 378 379 380 381 382 383 384
	/**
	 * Returns the declaration of named scopes.
	 * A named scope represents a query criteria that can be chained together with
	 * other named scopes and applied to a query. This method should be overridden
	 * by child classes to declare named scopes for the particular AR classes.
	 * For example, the following code declares two named scopes: 'recently' and
	 * 'published'.
	 * <pre>
	 * return array(
Qiang Xue committed
385 386 387 388 389 390 391
	 *	 'published'=>array(
	 *		   'condition'=>'status=1',
	 *	 ),
	 *	 'recently'=>array(
	 *		   'order'=>'create_time DESC',
	 *		   'limit'=>5,
	 *	 ),
w  
Qiang Xue committed
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
	 * );
	 * </pre>
	 * If the above scopes are declared in a 'Post' model, we can perform the following
	 * queries:
	 * <pre>
	 * $posts=Post::model()->published()->findAll();
	 * $posts=Post::model()->published()->recently()->findAll();
	 * $posts=Post::model()->published()->with('comments')->findAll();
	 * </pre>
	 * Note that the last query is a relational query.
	 *
	 * @return array the scope definition. The array keys are scope names; the array
	 * values are the corresponding scope definitions. Each scope definition is represented
	 * as an array whose keys must be properties of {@link CDbCriteria}.
	 */
Qiang Xue committed
407
	public static function scopes()
w  
Qiang Xue committed
408 409 410 411 412
	{
		return array();
	}

	/**
Qiang Xue committed
413
	 * PHP getter magic method.
Qiang Xue committed
414
	 * This method is overridden so that attributes and related objects can be accessed like properties.
Qiang Xue committed
415 416 417 418 419 420
	 * @param string $name property name
	 * @return mixed property value
	 * @see getAttribute
	 */
	public function __get($name)
	{
Qiang Xue committed
421
		if (isset($this->_attributes[$name])) {
Qiang Xue committed
422
			return $this->_attributes[$name];
Qiang Xue committed
423 424 425 426 427
		}
		$md = $this->getMetaData();
		if (isset($md->table->columns[$name])) {
			return null;
		} elseif (isset($md->relations[$name])) {
Qiang Xue committed
428
			if (isset($this->_related[$name]) || $this->_related !== null && array_key_exists($name, $this->_related)) {
Qiang Xue committed
429 430
				return $this->_related[$name];
			} else {
Qiang Xue committed
431
				return $this->_related[$name] = $this->findByRelation($md->relations[$name]);
Qiang Xue committed
432
			}
Qiang Xue committed
433
		}
Qiang Xue committed
434
		return parent::__get($name);
Qiang Xue committed
435 436 437 438 439 440 441 442 443 444
	}

	/**
	 * PHP setter magic method.
	 * This method is overridden so that AR attributes can be accessed like properties.
	 * @param string $name property name
	 * @param mixed $value property value
	 */
	public function __set($name, $value)
	{
Qiang Xue committed
445
		$md = $this->getMetaData();
Qiang Xue committed
446
		if (isset($md->table->columns[$name])) {
Qiang Xue committed
447
			$this->_attributes[$name] = $value;
Qiang Xue committed
448 449
		} elseif (isset($md->relations[$name])) {
			$this->_related[$name] = $value;
Qiang Xue committed
450 451 452 453 454 455 456 457 458 459 460 461 462
		} else {
			parent::__set($name, $value);
		}
	}

	/**
	 * Checks if a property value is null.
	 * This method overrides the parent implementation by checking
	 * if the named attribute is null or not.
	 * @param string $name the property name or the event name
	 * @return boolean whether the property value is null
	 */
	public function __isset($name)
w  
Qiang Xue committed
463
	{
Qiang Xue committed
464
		if (isset($this->_attributes[$name]) || isset($this->_related[$name])) {
Qiang Xue committed
465
			return true;
Qiang Xue committed
466 467 468
		}
		$md = $this->getMetaData();
		if (isset($md->table->columns[$name]) || isset($md->relations[$name])) {
Qiang Xue committed
469
			return false;
Qiang Xue committed
470
		} else {
Qiang Xue committed
471 472 473 474 475 476 477 478 479 480 481 482
			return parent::__isset($name);
		}
	}

	/**
	 * Sets a component property to be null.
	 * This method overrides the parent implementation by clearing
	 * the specified attribute value.
	 * @param string $name the property name or the event name
	 */
	public function __unset($name)
	{
Qiang Xue committed
483
		$md = $this->getMetaData();
Qiang Xue committed
484
		if (isset($md->table->columns[$name])) {
Qiang Xue committed
485
			unset($this->_attributes[$name]);
Qiang Xue committed
486 487
		} elseif (isset($md->relations[$name])) {
			unset($this->_related[$name]);
Qiang Xue committed
488
		} else {
Qiang Xue committed
489 490 491 492 493 494 495 496 497
			parent::__unset($name);
		}
	}

	/**
	 * Calls the named method which is not a class method.
	 * Do not call this method. This is a PHP magic method that we override
	 * to implement the named scope feature.
	 * @param string $name the method name
Qiang Xue committed
498
	 * @param array $params method parameters
Qiang Xue committed
499 500
	 * @return mixed the method return value
	 */
Qiang Xue committed
501
	public function __call($name, $params)
Qiang Xue committed
502
	{
Qiang Xue committed
503 504
		$md = $this->getMetaData();
		if (isset($md->relations[$name])) {
Qiang Xue committed
505
			return $this->findByRelation($md->relations[$name], isset($params[0]) ? $params[0] : array());
Qiang Xue committed
506
		}
Qiang Xue committed
507
		return parent::__call($name, $params);
Qiang Xue committed
508 509
	}

Qiang Xue committed
510 511
	/**
	 * Initializes the internal storage for the relation.
Qiang Xue committed
512
	 * This method is internally used by [[ActiveQuery]] when populating relation data.
Qiang Xue committed
513 514 515
	 * @param ActiveRelation $relation the relation object
	 */
	public function initRelation($relation)
Qiang Xue committed
516
	{
Qiang Xue committed
517
		$this->_related[$relation->name] = $relation->hasMany ? array() : null;
Qiang Xue committed
518 519
	}

Qiang Xue committed
520 521 522 523
	/**
	 * @param ActiveRelation $relation
	 * @param ActiveRecord $record
	 */
Qiang Xue committed
524 525 526
	public function addRelatedRecord($relation, $record)
	{
		if ($relation->hasMany) {
Qiang Xue committed
527 528
			if ($relation->index !== null) {
				$this->_related[$relation->name][$record->{$relation->index}] = $record;
Qiang Xue committed
529 530 531
			} else {
				$this->_related[$relation->name][] = $record;
			}
Qiang Xue committed
532
		} else {
Qiang Xue committed
533
			$this->_related[$relation->name] = $record;
Qiang Xue committed
534 535 536
		}
	}

Qiang Xue committed
537 538 539 540 541 542 543
	/**
	 * Returns the related record(s).
	 * This method will return the related record(s) of the current record.
	 * If the relation is HAS_ONE or BELONGS_TO, it will return a single object
	 * or null if the object does not exist.
	 * If the relation is HAS_MANY or MANY_MANY, it will return an array of objects
	 * or an empty array.
Qiang Xue committed
544
	 * @param ActiveRelation|string $relation the relation object or the name of the relation
Qiang Xue committed
545 546
	 * @param array $params additional parameters that customize the query conditions as specified in the relation declaration.
	 * @return mixed the related object(s).
Qiang Xue committed
547
	 * @throws Exception if the relation is not specified in [[relations()]].
Qiang Xue committed
548
	 */
Qiang Xue committed
549
	public function findByRelation($relation, $params = array())
Qiang Xue committed
550
	{
Qiang Xue committed
551 552 553 554
		if (is_string($relation)) {
			$md = $this->getMetaData();
			if (!isset($md->relations[$relation])) {
				throw new Exception(get_class($this) . ' has no relation named "' . $relation . '".');
Qiang Xue committed
555
			}
Qiang Xue committed
556
			$relation = $md->relations[$relation];
Qiang Xue committed
557
		}
Qiang Xue committed
558
		$relation = clone $relation;
Qiang Xue committed
559 560 561 562 563
		foreach ($params as $name => $value) {
			$relation->$name = $value;
		}

		$finder = new ActiveFinder($this->getDbConnection());
Qiang Xue committed
564
		return $finder->findWithRecord($this, $relation);
Qiang Xue committed
565 566 567 568 569 570 571 572 573
	}

	/**
	 * Returns the list of all attribute names of the model.
	 * This would return all column names of the table associated with this AR class.
	 * @return array list of attribute names.
	 */
	public function attributeNames()
	{
Qiang Xue committed
574
		return array_keys($this->getMetaData()->table->columns);
w  
Qiang Xue committed
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
	}

	/**
	 * Returns the named attribute value.
	 * If this is a new record and the attribute is not set before,
	 * the default column value will be returned.
	 * If this record is the result of a query and the attribute is not loaded,
	 * null will be returned.
	 * You may also use $this->AttributeName to obtain the attribute value.
	 * @param string $name the attribute name
	 * @return mixed the attribute value. Null if the attribute is not set or does not exist.
	 * @see hasAttribute
	 */
	public function getAttribute($name)
	{
Qiang Xue committed
590
		return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
w  
Qiang Xue committed
591 592 593 594 595 596 597 598 599 600 601
	}

	/**
	 * Sets the named attribute value.
	 * You may also use $this->AttributeName to set the attribute value.
	 * @param string $name the attribute name
	 * @param mixed $value the attribute value.
	 * @see hasAttribute
	 */
	public function setAttribute($name, $value)
	{
Qiang Xue committed
602
		$this->_attributes[$name] = $value;
w  
Qiang Xue committed
603 604 605 606 607
	}

	/**
	 * Returns all column attribute values.
	 * Note, related objects are not returned.
Qiang Xue committed
608
	 * @param null|array $names names of attributes whose value needs to be returned.
w  
Qiang Xue committed
609 610 611 612 613
	 * If this is true (default), then all attribute values will be returned, including
	 * those that are not loaded from DB (null will be returned for those attributes).
	 * If this is null, all attributes except those that are not loaded from DB will be returned.
	 * @return array attribute values indexed by attribute names.
	 */
Qiang Xue committed
614
	public function getAttributes($names = null)
w  
Qiang Xue committed
615
	{
Qiang Xue committed
616 617
		if ($names === null) {
			$names = $this->attributeNames();
w  
Qiang Xue committed
618
		}
Qiang Xue committed
619 620 621 622 623 624 625 626 627 628 629 630 631 632
		$values = array();
		foreach ($names as $name) {
			$values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
		}
		return $values;
	}

	public function getChangedAttributes($names = null)
	{
		if ($names === null) {
			$names = $this->attributeNames();
		}
		$names = array_flip($names);
		$attributes = array();
Qiang Xue committed
633
		if ($this->_oldAttributes === null) {
Qiang Xue committed
634 635 636 637 638 639 640 641 642 643
			foreach ($this->_attributes as $name => $value) {
				if (isset($names[$name])) {
					$attributes[$name] = $value;
				}
			}
		} else {
			foreach ($this->_attributes as $name => $value) {
				if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) {
					$attributes[$name] = $value;
				}
w  
Qiang Xue committed
644
			}
Qiang Xue committed
645
		}
Qiang Xue committed
646
		return $attributes;
w  
Qiang Xue committed
647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673
	}

	/**
	 * Saves the current record.
	 *
	 * The record is inserted as a row into the database table if its {@link isNewRecord}
	 * property is true (usually the case when the record is created using the 'new'
	 * operator). Otherwise, it will be used to update the corresponding row in the table
	 * (usually the case if the record is obtained using one of those 'find' methods.)
	 *
	 * Validation will be performed before saving the record. If the validation fails,
	 * the record will not be saved. You can call {@link getErrors()} to retrieve the
	 * validation errors.
	 *
	 * If the record is saved via insertion, its {@link isNewRecord} property will be
	 * set false, and its {@link scenario} property will be set to be 'update'.
	 * And if its primary key is auto-incremental and is not set before insertion,
	 * the primary key will be populated with the automatically generated key value.
	 *
	 * @param boolean $runValidation whether to perform validation before saving the record.
	 * If the validation fails, the record will not be saved to database.
	 * @param array $attributes list of attributes that need to be saved. Defaults to null,
	 * meaning all attributes that are loaded from DB will be saved.
	 * @return boolean whether the saving succeeds
	 */
	public function save($runValidation = true, $attributes = null)
	{
Qiang Xue committed
674
		if (!$runValidation || $this->validate($attributes)) {
w  
Qiang Xue committed
675
			return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes);
Qiang Xue committed
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793
		}
		return false;
	}

	/**
	 * Inserts a row into the table based on this active record attributes.
	 * If the table's primary key is auto-incremental and is null before insertion,
	 * it will be populated with the actual value after insertion.
	 * Note, validation is not performed in this method. You may call {@link validate} to perform the validation.
	 * After the record is inserted to DB successfully, its {@link isNewRecord} property will be set false,
	 * and its {@link scenario} property will be set to be 'update'.
	 * @param array $attributes list of attributes that need to be saved. Defaults to null,
	 * meaning all attributes that are loaded from DB will be saved.
	 * @return boolean whether the attributes are valid and the record is inserted successfully.
	 * @throws Exception if the record is not new
	 */
	public function insert($attributes = null)
	{
		if ($this->beforeInsert()) {
			$query = new Query;
			$values = $this->getChangedAttributes($attributes);
			$db = $this->getDbConnection();
			$command = $query->insert($this->tableName(), $values)->createCommand($db);
			if ($command->execute()) {
				$table = $this->getMetaData()->table;
				if ($table->sequenceName !== null) {
					foreach ($table->primaryKey as $name) {
						if (!isset($this->_attributes[$name])) {
							$this->_oldAttributes[$name] = $this->_attributes[$name] = $db->getLastInsertID($table->sequenceName);
							break;
						}
					}
				}
				foreach ($values as $name => $value) {
					$this->_oldAttributes[$name] = $value;
				}
				$this->afterInsert();
				return true;
			}
		}
		return false;
	}

	/**
	 * Updates the row represented by this active record.
	 * All loaded attributes will be saved to the database.
	 * Note, validation is not performed in this method. You may call {@link validate} to perform the validation.
	 * @param array $attributes list of attributes that need to be saved. Defaults to null,
	 * meaning all attributes that are loaded from DB will be saved.
	 * @return boolean whether the update is successful
	 * @throws Exception if the record is new
	 */
	public function update($attributes = null)
	{
		if ($this->getIsNewRecord()) {
			throw new Exception('The active record cannot be updated because it is new.');
		}
		if ($this->beforeUpdate()) {
			$values = $this->getChangedAttributes($attributes);
			if ($values !== array()) {
				$this->updateAll($values, $this->getOldPrimaryKey(true));
				foreach ($values as $name => $value) {
					$this->_oldAttributes[$name] = $this->_attributes[$name];
				}
				$this->afterUpdate();
			}
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Saves one or several counter columns for the current AR object.
	 * Note that this method differs from [[updateAllCounters()]] in that it only
	 * saves counters for the current AR object.
	 *
	 * An example usage is as follows:
	 *
	 * ~~~
	 * $post = Post::find($id)->one();
	 * $post->updateCounters(array('view_count' => 1));
	 * ~~~
	 *
	 * Use negative values if you want to decrease the counters.
	 * @param array $counters the counters to be updated (attribute name => increment value)
	 * @return boolean whether the saving is successful
	 * @throws Exception if the record is new or any database error
	 * @see updateAllCounters()
	 */
	public function updateCounters($counters)
	{
		if ($this->getIsNewRecord()) {
			throw new Exception('The active record cannot be updated because it is new.');
		}
		$this->updateAllCounters($counters, $this->getOldPrimaryKey(true));
		foreach ($counters as $name => $value) {
			$this->_attributes[$name] += $value;
			$this->_oldAttributes[$name] = $this->_attributes[$name];
		}
		return true;
	}

	/**
	 * Deletes the row corresponding to this active record.
	 * @return boolean whether the deletion is successful.
	 * @throws Exception if the record is new or any database error
	 */
	public function delete()
	{
		if ($this->getIsNewRecord()) {
			throw new Exception('The active record cannot be deleted because it is new.');
		}
		if ($this->beforeDelete()) {
			$result = $this->deleteAll($this->getPrimaryKey(true)) > 0;
			$this->_oldAttributes = null;
			$this->afterDelete();
			return $result;
Qiang Xue committed
794
		} else {
w  
Qiang Xue committed
795
			return false;
Qiang Xue committed
796
		}
w  
Qiang Xue committed
797 798 799 800 801 802 803 804 805 806 807
	}

	/**
	 * Returns if the current record is new.
	 * @return boolean whether the record is new and should be inserted when calling {@link save}.
	 * This property is automatically set in constructor and {@link populateRecord}.
	 * Defaults to false, but it will be set to true if the instance is created using
	 * the new operator.
	 */
	public function getIsNewRecord()
	{
Qiang Xue committed
808
		return $this->_oldAttributes === null;
w  
Qiang Xue committed
809 810 811 812 813 814 815 816 817
	}

	/**
	 * Sets if the record is new.
	 * @param boolean $value whether the record is new and should be inserted when calling {@link save}.
	 * @see getIsNewRecord
	 */
	public function setIsNewRecord($value)
	{
Qiang Xue committed
818
		$this->_oldAttributes = $value ? null : $this->_attributes;
w  
Qiang Xue committed
819 820 821 822
	}

	/**
	 * This event is raised before the record is saved.
Qiang Xue committed
823 824
	 * By setting [[\yii\base\ModelEvent::isValid]] to be false, the normal [[save()]] will be stopped.
	 * @param \yii\base\ModelEvent $event the event parameter
w  
Qiang Xue committed
825
	 */
Qiang Xue committed
826
	public function onBeforeInsert($event)
w  
Qiang Xue committed
827
	{
Qiang Xue committed
828
		$this->raiseEvent('onBeforeInsert', $event);
w  
Qiang Xue committed
829 830 831 832
	}

	/**
	 * This event is raised after the record is saved.
Qiang Xue committed
833
	 * @param \yii\base\Event $event the event parameter
w  
Qiang Xue committed
834
	 */
Qiang Xue committed
835
	public function onAfterInsert($event)
w  
Qiang Xue committed
836
	{
Qiang Xue committed
837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
		$this->raiseEvent('onAfterInsert', $event);
	}

	/**
	 * This event is raised before the record is saved.
	 * By setting [[\yii\base\ModelEvent::isValid]] to be false, the normal [[save()]] will be stopped.
	 * @param \yii\base\ModelEvent $event the event parameter
	 */
	public function onBeforeUpdate($event)
	{
		$this->raiseEvent('onBeforeUpdate', $event);
	}

	/**
	 * This event is raised after the record is saved.
	 * @param \yii\base\Event $event the event parameter
	 */
	public function onAfterUpdate($event)
	{
		$this->raiseEvent('onAfterUpdate', $event);
w  
Qiang Xue committed
857 858 859 860
	}

	/**
	 * This event is raised before the record is deleted.
Qiang Xue committed
861 862
	 * By setting [[\yii\base\ModelEvent::isValid]] to be false, the normal [[delete()]] process will be stopped.
	 * @param \yii\base\ModelEvent $event the event parameter
w  
Qiang Xue committed
863 864 865 866 867 868 869 870
	 */
	public function onBeforeDelete($event)
	{
		$this->raiseEvent('onBeforeDelete', $event);
	}

	/**
	 * This event is raised after the record is deleted.
Qiang Xue committed
871
	 * @param \yii\base\Event $event the event parameter
w  
Qiang Xue committed
872 873 874 875 876 877 878 879 880 881 882 883 884 885 886
	 */
	public function onAfterDelete($event)
	{
		$this->raiseEvent('onAfterDelete', $event);
	}

	/**
	 * This method is invoked before saving a record (after validation, if any).
	 * The default implementation raises the {@link onBeforeSave} event.
	 * You may override this method to do any preparation work for record saving.
	 * Use {@link isNewRecord} to determine whether the saving is
	 * for inserting or updating record.
	 * Make sure you call the parent implementation so that the event is raised properly.
	 * @return boolean whether the saving should be executed. Defaults to true.
	 */
Qiang Xue committed
887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914
	public function beforeInsert()
	{
		$event = new ModelEvent($this);
		$this->onBeforeInsert($event);
		return $event->isValid;
	}

	/**
	 * This method is invoked after saving a record successfully.
	 * The default implementation raises the {@link onAfterSave} event.
	 * You may override this method to do postprocessing after record saving.
	 * Make sure you call the parent implementation so that the event is raised properly.
	 */
	public function afterInsert()
	{
		$this->onAfterInsert(new Event($this));
	}

	/**
	 * This method is invoked before saving a record (after validation, if any).
	 * The default implementation raises the {@link onBeforeSave} event.
	 * You may override this method to do any preparation work for record saving.
	 * Use {@link isNewRecord} to determine whether the saving is
	 * for inserting or updating record.
	 * Make sure you call the parent implementation so that the event is raised properly.
	 * @return boolean whether the saving should be executed. Defaults to true.
	 */
	public function beforeUpdate()
w  
Qiang Xue committed
915
	{
Qiang Xue committed
916
		$event = new ModelEvent($this);
Qiang Xue committed
917
		$this->onBeforeUpdate($event);
Qiang Xue committed
918
		return $event->isValid;
w  
Qiang Xue committed
919 920 921 922 923 924 925 926
	}

	/**
	 * This method is invoked after saving a record successfully.
	 * The default implementation raises the {@link onAfterSave} event.
	 * You may override this method to do postprocessing after record saving.
	 * Make sure you call the parent implementation so that the event is raised properly.
	 */
Qiang Xue committed
927
	public function afterUpdate()
w  
Qiang Xue committed
928
	{
Qiang Xue committed
929
		$this->onAfterUpdate(new Event($this));
w  
Qiang Xue committed
930 931 932 933 934 935 936 937 938
	}

	/**
	 * This method is invoked before deleting a record.
	 * The default implementation raises the {@link onBeforeDelete} event.
	 * You may override this method to do any preparation work for record deletion.
	 * Make sure you call the parent implementation so that the event is raised properly.
	 * @return boolean whether the record should be deleted. Defaults to true.
	 */
Qiang Xue committed
939
	public function beforeDelete()
w  
Qiang Xue committed
940
	{
Qiang Xue committed
941 942 943
		$event = new ModelEvent($this);
		$this->onBeforeDelete($event);
		return $event->isValid;
w  
Qiang Xue committed
944 945 946 947 948 949 950 951
	}

	/**
	 * This method is invoked after deleting a record.
	 * The default implementation raises the {@link onAfterDelete} event.
	 * You may override this method to do postprocessing after the record is deleted.
	 * Make sure you call the parent implementation so that the event is raised properly.
	 */
Qiang Xue committed
952
	public function afterDelete()
w  
Qiang Xue committed
953
	{
Qiang Xue committed
954
		$this->onAfterDelete(new Event($this));
w  
Qiang Xue committed
955 956 957
	}

	/**
Qiang Xue committed
958
	 * Repopulates this active record with the latest data.
Qiang Xue committed
959
	 * @param array $attributes
Qiang Xue committed
960
	 * @return boolean whether the row still exists in the database. If true, the latest data will be populated to this active record.
w  
Qiang Xue committed
961
	 */
Qiang Xue committed
962
	public function refresh($attributes = null)
w  
Qiang Xue committed
963
	{
Qiang Xue committed
964 965 966 967 968 969 970 971 972
		if ($this->getIsNewRecord()) {
			return false;
		}
		$record = $this->find()->where($this->getPrimaryKey(true))->one();
		if ($record === null) {
			return false;
		}
		if ($attributes === null) {
			foreach ($this->attributeNames() as $name) {
Qiang Xue committed
973
				$this->_attributes[$name] = $record->_attributes[$name];
Qiang Xue committed
974
			}
Qiang Xue committed
975
			$this->_oldAttributes = $this->_attributes;
Qiang Xue committed
976
		} else {
Qiang Xue committed
977 978 979
			foreach ($attributes as $name) {
				$this->_oldAttributes[$name] = $this->_attributes[$name] = $record->_attributes[$name];
			}
w  
Qiang Xue committed
980
		}
Qiang Xue committed
981
		return true;
w  
Qiang Xue committed
982 983 984
	}

	/**
Qiang Xue committed
985 986 987 988
	 * Compares current active record with another one.
	 * The comparison is made by comparing table name and the primary key values of the two active records.
	 * @param ActiveRecord $record record to compare to
	 * @return boolean whether the two active records refer to the same row in the database table.
w  
Qiang Xue committed
989
	 */
Qiang Xue committed
990
	public function equals($record)
w  
Qiang Xue committed
991
	{
Qiang Xue committed
992
		return $this->tableName() === $record->tableName() && $this->getPrimaryKey() === $record->getPrimaryKey();
w  
Qiang Xue committed
993 994 995
	}

	/**
Qiang Xue committed
996
	 * Returns the primary key value.
Qiang Xue committed
997 998
	 * @param boolean $asArray whether to return the primary key value as an array. If true,
	 * the return value will be an array with column name as key and column value as value.
Qiang Xue committed
999 1000
	 * @return mixed the primary key value. An array (column name=>column value) is returned if the primary key is composite.
	 * If primary key is not defined, null will be returned.
w  
Qiang Xue committed
1001
	 */
Qiang Xue committed
1002
	public function getPrimaryKey($asArray = false)
w  
Qiang Xue committed
1003
	{
Qiang Xue committed
1004
		$table = static::getMetaData()->table;
Qiang Xue committed
1005
		if (count($table->primaryKey) === 1 && !$asArray) {
Qiang Xue committed
1006
			return isset($this->_attributes[$table->primaryKey[0]]) ? $this->_attributes[$table->primaryKey[0]] : null;
Qiang Xue committed
1007
		} else {
Qiang Xue committed
1008
			$values = array();
Qiang Xue committed
1009
			foreach ($table->primaryKey as $name) {
Qiang Xue committed
1010
				$values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
Qiang Xue committed
1011 1012
			}
			return $values;
w  
Qiang Xue committed
1013 1014 1015 1016
		}
	}

	/**
Qiang Xue committed
1017 1018 1019 1020
	 * Returns the old primary key value.
	 * This refers to the primary key value that is populated into the record
	 * after executing a find method (e.g. find(), findAll()).
	 * The value remains unchanged even if the primary key attribute is manually assigned with a different value.
Qiang Xue committed
1021 1022
	 * @param boolean $asArray whether to return the primary key value as an array. If true,
	 * the return value will be an array with column name as key and column value as value.
Qiang Xue committed
1023 1024
	 * If this is false (default), a scalar value will be returned for non-composite primary key.
	 * @return string|array the old primary key value. An array (column name=>column value) is returned if the primary key is composite.
Qiang Xue committed
1025
	 * If primary key is not defined, null will be returned.
w  
Qiang Xue committed
1026
	 */
Qiang Xue committed
1027
	public function getOldPrimaryKey($asArray = false)
w  
Qiang Xue committed
1028
	{
Qiang Xue committed
1029 1030 1031 1032 1033 1034 1035 1036 1037 1038
		$table = static::getMetaData()->table;
		if (count($table->primaryKey) === 1 && !$asArray) {
			return isset($this->_oldAttributes[$table->primaryKey[0]]) ? $this->_oldAttributes[$table->primaryKey[0]] : null;
		} else {
			$values = array();
			foreach ($table->primaryKey as $name) {
				$values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
			}
			return $values;
		}
w  
Qiang Xue committed
1039 1040 1041 1042
	}

	/**
	 * Creates an active record with the given attributes.
Qiang Xue committed
1043 1044
	 * @param array $row attribute values (name => value)
	 * @return ActiveRecord the newly created active record.
w  
Qiang Xue committed
1045
	 */
Qiang Xue committed
1046
	public static function create($row)
w  
Qiang Xue committed
1047
	{
Qiang Xue committed
1048 1049 1050
		$record = static::instantiate($row);
		$columns = static::getMetaData()->table->columns;
		foreach ($row as $name => $value) {
Qiang Xue committed
1051
			if (isset($columns[$name])) {
Qiang Xue committed
1052
				$record->_attributes[$name] = $value;
Qiang Xue committed
1053
			} else {
Qiang Xue committed
1054
				$record->$name = $value;
w  
Qiang Xue committed
1055 1056
			}
		}
Qiang Xue committed
1057
		$record->_oldAttributes = $record->_attributes;
Qiang Xue committed
1058
		return $record;
w  
Qiang Xue committed
1059 1060 1061 1062
	}

	/**
	 * Creates an active record instance.
Qiang Xue committed
1063
	 * This method is called by [[createRecord()]].
w  
Qiang Xue committed
1064 1065 1066 1067
	 * You may override this method if the instance being created
	 * depends the attributes that are to be populated to the record.
	 * For example, by creating a record based on the value of a column,
	 * you may implement the so-called single-table inheritance mapping.
Alexander Makarov committed
1068
	 * @param array $row list of attribute values for the active records.
w  
Qiang Xue committed
1069
	 * @return ActiveRecord the active record
w  
Qiang Xue committed
1070
	 */
Qiang Xue committed
1071
	public static function instantiate($row)
w  
Qiang Xue committed
1072
	{
Qiang Xue committed
1073
		return static::newInstance();
w  
Qiang Xue committed
1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086
	}

	/**
	 * Returns whether there is an element at the specified offset.
	 * This method is required by the interface ArrayAccess.
	 * @param mixed $offset the offset to check on
	 * @return boolean
	 */
	public function offsetExists($offset)
	{
		return $this->__isset($offset);
	}
}