ActiveRecord.php 38.2 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
use yii\db\dao\Expression;
Qiang Xue committed
21
use yii\util\StringHelper;
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
 * @property array $attributes attribute values indexed by attribute names
30
 *
31 32 33
 * ActiveRecord provides a set of events for further customization:
 *
 * - `beforeInsert`. Raised before the record is saved.
34
 *   By setting [[\yii\base\ModelEvent::isValid]] to be false, the normal [[save()]] will be stopped.
35 36
 * - `afterInsert`. Raised after the record is saved.
 * - `beforeUpdate`. Raised before the record is saved.
37
 *   By setting [[\yii\base\ModelEvent::isValid]] to be false, the normal [[save()]] will be stopped.
38 39
 * - `afterUpdate`. Raised after the record is saved.
 * - `beforeDelete`. Raised before the record is deleted.
40
 *   By setting [[\yii\base\ModelEvent::isValid]] to be false, the normal [[delete()]] process will be stopped.
41
 * - `afterDelete`. Raised after the record is deleted.
42
 *
w  
Qiang Xue committed
43
 */
Qiang Xue committed
44
abstract class ActiveRecord extends Model
w  
Qiang Xue committed
45 46
{
	/**
Qiang Xue committed
47 48 49 50 51
	 * @var array attribute values indexed by attribute names
	 */
	private $_attributes = array();
	/**
	 * @var array old attribute values indexed by attribute names.
w  
Qiang Xue committed
52
	 */
Qiang Xue committed
53
	private $_oldAttributes;
Qiang Xue committed
54 55 56 57
	/**
	 * @var array related records indexed by relation names.
	 */
	private $_related;
w  
Qiang Xue committed
58 59

	/**
Qiang Xue committed
60 61 62
	 * 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
63
	 */
Qiang Xue committed
64
	public static function getMetaData($refresh = false)
w  
Qiang Xue committed
65
	{
Qiang Xue committed
66
		return ActiveMetaData::getInstance(get_called_class(), $refresh);
w  
Qiang Xue committed
67 68
	}

Qiang Xue committed
69 70 71 72 73 74 75 76 77 78 79
	/**
	 * 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
80
	/**
Qiang Xue committed
81
	 * Creates an [[ActiveQuery]] instance for query purpose.
Qiang Xue committed
82
	 *
Qiang Xue committed
83 84
	 * Because [[ActiveQuery]] implements a set of query building methods,
	 * additional query conditions can be specified by calling the methods of [[ActiveQuery]].
Qiang Xue committed
85 86 87 88 89 90
	 *
	 * Below are some usage examples:
	 *
	 * ~~~
	 * // find all customers
	 * $customers = Customer::find()->all();
Qiang Xue committed
91
	 * // find a single customer whose primary key value is 10
Qiang Xue committed
92 93 94
	 * $customer = Customer::find(10);
	 * // the above is equivalent to:
	 * Customer::find()->where(array('id' => 10))->one();
Qiang Xue committed
95
	 * // find all active customers and order them by their age:
Qiang Xue committed
96 97
	 * $customers = Customer::find()
	 *     ->where(array('status' => 1))
98
	 *     ->order('age')
Qiang Xue committed
99 100 101 102
	 *     ->all();
	 * // or alternatively:
	 * $customers = Customer::find(array(
	 *     'where' => array('status' => 1),
103
	 *     'order' => 'age',
Qiang Xue committed
104
	 * ))->all();
Qiang Xue committed
105 106 107 108
	 * ~~~
	 *
	 * @param mixed $q the query parameter. This can be one of the followings:
	 *
Qiang Xue committed
109 110
	 *  - 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
111
	 *
Qiang Xue committed
112 113 114
	 * @return ActiveQuery|ActiveRecord|null the [[ActiveQuery]] instance for query purpose, or
	 * the ActiveRecord object when a scalar is passed to this method which is considered to be a
	 * primary key value (null will be returned if no record is found in this case.)
Qiang Xue committed
115 116 117
	 */
	public static function find($q = null)
	{
Qiang Xue committed
118 119 120 121 122
		$query = static::createActiveQuery();
		if (is_array($q)) {
			foreach ($q as $name => $value) {
				$query->$name = $value;
			}
Qiang Xue committed
123 124
		} elseif ($q !== null) {
			// query by primary key
Qiang Xue committed
125
			$primaryKey = static::getMetaData()->table->primaryKey;
Qiang Xue committed
126
			return $query->where(array($primaryKey[0] => $q))->one();
Qiang Xue committed
127
		}
Qiang Xue committed
128
		return $query;
w  
Qiang Xue committed
129 130
	}

Qiang Xue committed
131
	/**
Qiang Xue committed
132
	 * Creates an [[ActiveQuery]] instance and query by a given SQL statement.
Qiang Xue committed
133
	 * Note that because the SQL statement is already specified, calling further
134
	 * query methods (such as `where()`, `order()`) on [[ActiveQuery]] will have no effect.
Qiang Xue committed
135
	 * Methods such as `with()`, `asArray()` can still be called though.
Qiang Xue committed
136 137
	 * @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
138
	 * @return ActiveQuery the [[ActiveQuery]] instance
Qiang Xue committed
139
	 */
Qiang Xue committed
140
	public static function findBySql($sql, $params = array())
w  
Qiang Xue committed
141
	{
Qiang Xue committed
142 143 144 145 146 147 148 149 150 151 152 153
		$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
154
	 * echo Customer::count()->value();
Qiang Xue committed
155 156 157
	 * // count the number of active customers:
	 * echo Customer::count(array(
	 *     'where' => array('status' => 1),
Qiang Xue committed
158 159 160 161 162 163 164
	 * ))->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
165 166
	 * ~~~
	 *
167 168
	 * @param array $q the query configuration. This should be an array of name-value pairs.
	 * It will be used to configure the [[ActiveQuery]] object for query purpose.
Qiang Xue committed
169 170 171 172 173 174 175 176 177 178 179 180
	 *
	 * @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
181
			$query->select = array('COUNT(*)');
Qiang Xue committed
182 183
		}
		return $query->value();
w  
Qiang Xue committed
184 185
	}

Qiang Xue committed
186 187 188 189 190 191 192 193
	/**
	 * 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
194
	public static function updateAll($attributes, $condition = '', $params = array())
w  
Qiang Xue committed
195
	{
Qiang Xue committed
196
		$query = new Query;
Qiang Xue committed
197 198
		$query->update(static::tableName(), $attributes, $condition, $params);
		return $query->createCommand(static::getDbConnection())->execute();
w  
Qiang Xue committed
199 200
	}

Qiang Xue committed
201 202 203 204 205 206 207 208 209
	/**
	 * 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
210
	{
Qiang Xue committed
211
		$db = static::getDbConnection();
Qiang Xue committed
212 213 214 215 216 217
		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
218 219
		$query->update(static::tableName(), $counters, $condition, $params);
		return $query->createCommand($db)->execute();
w  
Qiang Xue committed
220 221
	}

Qiang Xue committed
222 223 224 225 226 227 228
	/**
	 * 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
229
	public static function deleteAll($condition = '', $params = array())
w  
Qiang Xue committed
230
	{
Qiang Xue committed
231
		$query = new Query;
Qiang Xue committed
232 233
		$query->delete(static::tableName(), $condition, $params);
		return $query->createCommand(static::getDbConnection())->execute();
w  
Qiang Xue committed
234 235
	}

.  
Qiang Xue committed
236
	/**
Qiang Xue committed
237 238 239
	 * 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
240
	 */
Qiang Xue committed
241
	public static function createActiveQuery()
w  
Qiang Xue committed
242
	{
Qiang Xue committed
243
		return new ActiveQuery(get_called_class());
w  
Qiang Xue committed
244 245 246
	}

	/**
Qiang Xue committed
247
	 * Declares the name of the database table associated with this AR class.
Qiang Xue committed
248
	 * By default this method returns the class name as the table name by calling [[StringHelper::camel2id()]].
Qiang Xue committed
249
	 * For example, 'Customer' becomes 'customer', and 'OrderDetail' becomes 'order_detail'.
w  
Qiang Xue committed
250 251 252
	 * You may override this method if the table is not named after this convention.
	 * @return string the table name
	 */
Qiang Xue committed
253
	public static function tableName()
w  
Qiang Xue committed
254
	{
Qiang Xue committed
255
		return StringHelper::camel2id(basename(get_called_class()), '_');
w  
Qiang Xue committed
256 257 258
	}

	/**
Qiang Xue committed
259
	 * Declares the primary key name for this AR class.
Qiang Xue committed
260 261
	 * 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
262
	 * you do not need to override this method. The default implementation simply returns null,
Qiang Xue committed
263 264
	 * 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
265 266 267 268
	 * 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
269
	public static function primaryKey()
w  
Qiang Xue committed
270 271 272 273
	{
	}

	/**
Qiang Xue committed
274
	 * Declares the relations for this AR class.
Qiang Xue committed
275
	 *
Qiang Xue committed
276
	 * Child classes may override this method to specify their relations.
Qiang Xue committed
277
	 *
Qiang Xue committed
278
	 * The following code shows how to declare relations for a `Programmer` AR class:
Qiang Xue committed
279 280 281
	 *
	 * ~~~
	 * return array(
Qiang Xue committed
282
	 *	 'manager:Manager' => '@.id = ?.manager_id',
Qiang Xue committed
283
	 *	 'assignments:Assignment[]' => array(
Qiang Xue committed
284
	 *		 'on' => '@.owner_id = ?.id AND @.status = 1',
285
	 *		 'order' => '@.create_time DESC',
Qiang Xue committed
286 287 288
	 *	 ),
	 *	 'projects:Project[]' => array(
	 *		 'via' => 'assignments',
Qiang Xue committed
289
	 *		 'on' => '@.id = ?.project_id',
Qiang Xue committed
290
	 *	 ),
Qiang Xue committed
291 292 293
	 * );
	 * ~~~
	 *
w  
Qiang Xue committed
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 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
	 * 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
336
	 * <li>'params': the parameters to be bound to the generated SQL statement.
w  
Qiang Xue committed
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
	 *   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
364 365 366
	 *	 '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
367 368 369 370 371
	 * );
	 * </pre>
	 *
	 * @return array list of related object declarations. Defaults to empty array.
	 */
Qiang Xue committed
372
	public static function relations()
w  
Qiang Xue committed
373 374 375 376
	{
		return array();
	}

Qiang Xue committed
377 378 379 380 381
	/**
	 * 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
382 383
	 * @param BaseActiveQuery
	 * @return BaseActiveQuery the query criteria. This will be used as the parameter to the constructor
Qiang Xue committed
384 385
	 * of {@link CDbCriteria}.
	 */
Qiang Xue committed
386
	public static function defaultScope($query)
Qiang Xue committed
387
	{
Qiang Xue committed
388
		return $query;
Qiang Xue committed
389 390
	}

w  
Qiang Xue committed
391 392 393 394 395 396 397 398 399
	/**
	 * 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
400 401 402 403 404 405 406
	 *	 'published'=>array(
	 *		   'condition'=>'status=1',
	 *	 ),
	 *	 'recently'=>array(
	 *		   'order'=>'create_time DESC',
	 *		   'limit'=>5,
	 *	 ),
w  
Qiang Xue committed
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
	 * );
	 * </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
422
	public static function scopes()
w  
Qiang Xue committed
423 424 425 426 427
	{
		return array();
	}

	/**
Qiang Xue committed
428
	 * PHP getter magic method.
Qiang Xue committed
429
	 * This method is overridden so that attributes and related objects can be accessed like properties.
Qiang Xue committed
430 431 432 433 434 435
	 * @param string $name property name
	 * @return mixed property value
	 * @see getAttribute
	 */
	public function __get($name)
	{
Qiang Xue committed
436
		if (isset($this->_attributes[$name])) {
Qiang Xue committed
437
			return $this->_attributes[$name];
Qiang Xue committed
438 439 440 441 442
		}
		$md = $this->getMetaData();
		if (isset($md->table->columns[$name])) {
			return null;
		} elseif (isset($md->relations[$name])) {
Qiang Xue committed
443
			if (isset($this->_related[$name]) || $this->_related !== null && array_key_exists($name, $this->_related)) {
Qiang Xue committed
444 445
				return $this->_related[$name];
			} else {
Qiang Xue committed
446
				return $this->_related[$name] = $this->findByRelation($md->relations[$name]);
Qiang Xue committed
447
			}
Qiang Xue committed
448
		}
Qiang Xue committed
449
		return parent::__get($name);
Qiang Xue committed
450 451 452 453 454 455 456 457 458 459
	}

	/**
	 * 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
460
		$md = $this->getMetaData();
Qiang Xue committed
461
		if (isset($md->table->columns[$name])) {
Qiang Xue committed
462
			$this->_attributes[$name] = $value;
Qiang Xue committed
463 464
		} elseif (isset($md->relations[$name])) {
			$this->_related[$name] = $value;
Qiang Xue committed
465 466 467 468 469 470 471 472 473 474 475 476 477
		} 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
478
	{
Qiang Xue committed
479
		if (isset($this->_attributes[$name]) || isset($this->_related[$name])) {
Qiang Xue committed
480
			return true;
Qiang Xue committed
481 482 483
		}
		$md = $this->getMetaData();
		if (isset($md->table->columns[$name]) || isset($md->relations[$name])) {
Qiang Xue committed
484
			return false;
Qiang Xue committed
485
		} else {
Qiang Xue committed
486 487 488 489 490 491 492 493 494 495 496 497
			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
498
		$md = $this->getMetaData();
Qiang Xue committed
499
		if (isset($md->table->columns[$name])) {
Qiang Xue committed
500
			unset($this->_attributes[$name]);
Qiang Xue committed
501 502
		} elseif (isset($md->relations[$name])) {
			unset($this->_related[$name]);
Qiang Xue committed
503
		} else {
Qiang Xue committed
504 505 506 507 508 509 510 511 512
			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
513
	 * @param array $params method parameters
Qiang Xue committed
514 515
	 * @return mixed the method return value
	 */
Qiang Xue committed
516
	public function __call($name, $params)
Qiang Xue committed
517
	{
Qiang Xue committed
518 519
		$md = $this->getMetaData();
		if (isset($md->relations[$name])) {
Qiang Xue committed
520
			return $this->findByRelation($md->relations[$name], isset($params[0]) ? $params[0] : array());
Qiang Xue committed
521
		}
Qiang Xue committed
522
		return parent::__call($name, $params);
Qiang Xue committed
523 524
	}

Qiang Xue committed
525 526
	/**
	 * Initializes the internal storage for the relation.
Qiang Xue committed
527
	 * This method is internally used by [[ActiveQuery]] when populating relation data.
Qiang Xue committed
528 529 530
	 * @param ActiveRelation $relation the relation object
	 */
	public function initRelation($relation)
Qiang Xue committed
531
	{
Qiang Xue committed
532
		$this->_related[$relation->name] = $relation->hasMany ? array() : null;
Qiang Xue committed
533 534
	}

Qiang Xue committed
535 536 537 538
	/**
	 * @param ActiveRelation $relation
	 * @param ActiveRecord $record
	 */
Qiang Xue committed
539 540 541
	public function addRelatedRecord($relation, $record)
	{
		if ($relation->hasMany) {
Qiang Xue committed
542 543
			if ($relation->index !== null) {
				$this->_related[$relation->name][$record->{$relation->index}] = $record;
Qiang Xue committed
544 545 546
			} else {
				$this->_related[$relation->name][] = $record;
			}
Qiang Xue committed
547
		} else {
Qiang Xue committed
548
			$this->_related[$relation->name] = $record;
Qiang Xue committed
549 550 551
		}
	}

Qiang Xue committed
552 553 554 555 556 557 558
	/**
	 * 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
559
	 * @param ActiveRelation|string $relation the relation object or the name of the relation
560
	 * @param array|\Closure $params additional parameters that customize the query conditions as specified in the relation declaration.
Qiang Xue committed
561
	 * @return mixed the related object(s).
Qiang Xue committed
562
	 * @throws Exception if the relation is not specified in [[relations()]].
Qiang Xue committed
563
	 */
Qiang Xue committed
564
	public function findByRelation($relation, $params = array())
Qiang Xue committed
565
	{
Qiang Xue committed
566 567 568 569
		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
570
			}
Qiang Xue committed
571
			$relation = $md->relations[$relation];
Qiang Xue committed
572
		}
Qiang Xue committed
573
		$relation = clone $relation;
574
		if ($params instanceof \Closure) {
Qiang Xue committed
575
			$params($relation);
576 577 578 579
		} else {
			foreach ($params as $name => $value) {
				$relation->$name = $value;
			}
Qiang Xue committed
580 581 582
		}

		$finder = new ActiveFinder($this->getDbConnection());
Qiang Xue committed
583
		return $finder->findWithRecord($this, $relation);
Qiang Xue committed
584 585 586 587 588 589 590
	}

	/**
	 * 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.
	 */
591
	public function attributes()
Qiang Xue committed
592
	{
Qiang Xue committed
593
		return array_keys($this->getMetaData()->table->columns);
w  
Qiang Xue committed
594 595 596 597 598 599 600 601 602 603 604 605 606 607 608
	}

	/**
	 * 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
609
		return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
w  
Qiang Xue committed
610 611 612 613 614 615 616 617 618 619 620
	}

	/**
	 * 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
621
		$this->_attributes[$name] = $value;
w  
Qiang Xue committed
622 623 624 625 626
	}

	/**
	 * Returns all column attribute values.
	 * Note, related objects are not returned.
Qiang Xue committed
627
	 * @param null|array $names names of attributes whose value needs to be returned.
w  
Qiang Xue committed
628 629 630 631 632
	 * 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
633
	public function getAttributes($names = null)
w  
Qiang Xue committed
634
	{
Qiang Xue committed
635
		if ($names === null) {
636
			$names = $this->attributes();
w  
Qiang Xue committed
637
		}
Qiang Xue committed
638 639 640 641 642 643 644 645 646 647
		$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) {
648
			$names = $this->attributes();
Qiang Xue committed
649 650 651
		}
		$names = array_flip($names);
		$attributes = array();
Qiang Xue committed
652
		if ($this->_oldAttributes === null) {
Qiang Xue committed
653 654 655 656 657 658 659 660 661 662
			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
663
			}
Qiang Xue committed
664
		}
Qiang Xue committed
665
		return $attributes;
w  
Qiang Xue committed
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
	}

	/**
	 * 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
693
		if (!$runValidation || $this->validate($attributes)) {
w  
Qiang Xue committed
694
			return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes);
Qiang Xue committed
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 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812
		}
		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
813
		} else {
w  
Qiang Xue committed
814
			return false;
Qiang Xue committed
815
		}
w  
Qiang Xue committed
816 817 818 819 820 821 822 823 824 825 826
	}

	/**
	 * 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
827
		return $this->_oldAttributes === null;
w  
Qiang Xue committed
828 829 830 831 832 833 834 835 836
	}

	/**
	 * 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
837
		$this->_oldAttributes = $value ? null : $this->_attributes;
w  
Qiang Xue committed
838 839 840 841
	}

	/**
	 * This method is invoked before saving a record (after validation, if any).
842
	 * The default implementation raises the `beforeSave` event.
w  
Qiang Xue committed
843 844 845 846 847 848
	 * 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
849 850 851
	public function beforeInsert()
	{
		$event = new ModelEvent($this);
852
		$this->trigger('beforeInsert', $event);
Qiang Xue committed
853 854 855 856 857
		return $event->isValid;
	}

	/**
	 * This method is invoked after saving a record successfully.
858
	 * The default implementation raises the `afterSave` event.
Qiang Xue committed
859 860 861 862 863
	 * 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()
	{
864
		$this->trigger('afterInsert', new Event($this));
Qiang Xue committed
865 866 867 868
	}

	/**
	 * This method is invoked before saving a record (after validation, if any).
869
	 * The default implementation raises the `beforeSave` event.
Qiang Xue committed
870 871 872 873 874 875 876
	 * 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
877
	{
Qiang Xue committed
878
		$event = new ModelEvent($this);
879
		$this->trigger('beforeUpdate', $event);
Qiang Xue committed
880
		return $event->isValid;
w  
Qiang Xue committed
881 882 883 884
	}

	/**
	 * This method is invoked after saving a record successfully.
885
	 * The default implementation raises the `afterSave` event.
w  
Qiang Xue committed
886 887 888
	 * 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
889
	public function afterUpdate()
w  
Qiang Xue committed
890
	{
891
		$this->trigger('afterUpdate', new Event($this));
w  
Qiang Xue committed
892 893 894 895
	}

	/**
	 * This method is invoked before deleting a record.
896
	 * The default implementation raises the `beforeDelete` event.
w  
Qiang Xue committed
897 898 899 900
	 * 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
901
	public function beforeDelete()
w  
Qiang Xue committed
902
	{
Qiang Xue committed
903
		$event = new ModelEvent($this);
904
		$this->trigger('beforeDelete', $event);
Qiang Xue committed
905
		return $event->isValid;
w  
Qiang Xue committed
906 907 908 909
	}

	/**
	 * This method is invoked after deleting a record.
910
	 * The default implementation raises the `afterDelete` event.
w  
Qiang Xue committed
911 912 913
	 * 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
914
	public function afterDelete()
w  
Qiang Xue committed
915
	{
916
		$this->trigger('afterDelete', new Event($this));
w  
Qiang Xue committed
917 918 919
	}

	/**
Qiang Xue committed
920
	 * Repopulates this active record with the latest data.
Qiang Xue committed
921
	 * @param array $attributes
Qiang Xue committed
922
	 * @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
923
	 */
Qiang Xue committed
924
	public function refresh($attributes = null)
w  
Qiang Xue committed
925
	{
Qiang Xue committed
926 927 928 929 930 931 932 933
		if ($this->getIsNewRecord()) {
			return false;
		}
		$record = $this->find()->where($this->getPrimaryKey(true))->one();
		if ($record === null) {
			return false;
		}
		if ($attributes === null) {
934
			foreach ($this->attributes() as $name) {
Qiang Xue committed
935
				$this->_attributes[$name] = $record->_attributes[$name];
Qiang Xue committed
936
			}
Qiang Xue committed
937
			$this->_oldAttributes = $this->_attributes;
Qiang Xue committed
938
		} else {
Qiang Xue committed
939 940 941
			foreach ($attributes as $name) {
				$this->_oldAttributes[$name] = $this->_attributes[$name] = $record->_attributes[$name];
			}
w  
Qiang Xue committed
942
		}
Qiang Xue committed
943
		return true;
w  
Qiang Xue committed
944 945 946
	}

	/**
Qiang Xue committed
947 948 949 950
	 * 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
951
	 */
Qiang Xue committed
952
	public function equals($record)
w  
Qiang Xue committed
953
	{
Qiang Xue committed
954
		return $this->tableName() === $record->tableName() && $this->getPrimaryKey() === $record->getPrimaryKey();
w  
Qiang Xue committed
955 956 957
	}

	/**
Qiang Xue committed
958
	 * Returns the primary key value.
Qiang Xue committed
959 960
	 * @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
961 962
	 * @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
963
	 */
Qiang Xue committed
964
	public function getPrimaryKey($asArray = false)
w  
Qiang Xue committed
965
	{
Qiang Xue committed
966
		$table = static::getMetaData()->table;
Qiang Xue committed
967
		if (count($table->primaryKey) === 1 && !$asArray) {
Qiang Xue committed
968
			return isset($this->_attributes[$table->primaryKey[0]]) ? $this->_attributes[$table->primaryKey[0]] : null;
Qiang Xue committed
969
		} else {
Qiang Xue committed
970
			$values = array();
Qiang Xue committed
971
			foreach ($table->primaryKey as $name) {
Qiang Xue committed
972
				$values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
Qiang Xue committed
973 974
			}
			return $values;
w  
Qiang Xue committed
975 976 977 978
		}
	}

	/**
Qiang Xue committed
979 980 981 982
	 * 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
983 984
	 * @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
985 986
	 * 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
987
	 * If primary key is not defined, null will be returned.
w  
Qiang Xue committed
988
	 */
Qiang Xue committed
989
	public function getOldPrimaryKey($asArray = false)
w  
Qiang Xue committed
990
	{
Qiang Xue committed
991 992 993 994 995 996 997 998 999 1000
		$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
1001 1002 1003 1004
	}

	/**
	 * Creates an active record with the given attributes.
Qiang Xue committed
1005 1006
	 * @param array $row attribute values (name => value)
	 * @return ActiveRecord the newly created active record.
w  
Qiang Xue committed
1007
	 */
Qiang Xue committed
1008
	public static function create($row)
w  
Qiang Xue committed
1009
	{
Qiang Xue committed
1010 1011 1012
		$record = static::instantiate($row);
		$columns = static::getMetaData()->table->columns;
		foreach ($row as $name => $value) {
Qiang Xue committed
1013
			if (isset($columns[$name])) {
Qiang Xue committed
1014
				$record->_attributes[$name] = $value;
Qiang Xue committed
1015
			} else {
Qiang Xue committed
1016
				$record->$name = $value;
w  
Qiang Xue committed
1017 1018
			}
		}
Qiang Xue committed
1019
		$record->_oldAttributes = $record->_attributes;
Qiang Xue committed
1020
		return $record;
w  
Qiang Xue committed
1021 1022 1023 1024
	}

	/**
	 * Creates an active record instance.
Qiang Xue committed
1025
	 * This method is called by [[createRecord()]].
w  
Qiang Xue committed
1026 1027 1028 1029
	 * 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
1030
	 * @param array $row list of attribute values for the active records.
w  
Qiang Xue committed
1031
	 * @return ActiveRecord the active record
w  
Qiang Xue committed
1032
	 */
Qiang Xue committed
1033
	public static function instantiate($row)
w  
Qiang Xue committed
1034
	{
Qiang Xue committed
1035 1036
		$class = get_called_class();
		return new $class;
w  
Qiang Xue committed
1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049
	}

	/**
	 * 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);
	}
}