ActiveRecord.php 38.3 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 169 170 171 172 173 174 175 176 177 178 179 180 181 182
	 * ~~~
	 *
	 * @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
183
			$query->select = array('COUNT(*)');
Qiang Xue committed
184 185
		}
		return $query->value();
w  
Qiang Xue committed
186 187
	}

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

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

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

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

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

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

	/**
Qiang Xue committed
276
	 * Declares the relations for this AR class.
Qiang Xue committed
277
	 *
Qiang Xue committed
278
	 * Child classes may override this method to specify their relations.
Qiang Xue committed
279
	 *
Qiang Xue committed
280
	 * The following code shows how to declare relations for a `Programmer` AR class:
Qiang Xue committed
281 282 283
	 *
	 * ~~~
	 * return array(
Qiang Xue committed
284
	 *	 'manager:Manager' => '@.id = ?.manager_id',
Qiang Xue committed
285
	 *	 'assignments:Assignment[]' => array(
Qiang Xue committed
286
	 *		 'on' => '@.owner_id = ?.id AND @.status = 1',
287
	 *		 'order' => '@.create_time DESC',
Qiang Xue committed
288 289 290
	 *	 ),
	 *	 'projects:Project[]' => array(
	 *		 'via' => 'assignments',
Qiang Xue committed
291
	 *		 'on' => '@.id = ?.project_id',
Qiang Xue committed
292
	 *	 ),
Qiang Xue committed
293 294 295
	 * );
	 * ~~~
	 *
w  
Qiang Xue committed
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 336 337
	 * 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
338
	 * <li>'params': the parameters to be bound to the generated SQL statement.
w  
Qiang Xue committed
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 364 365
	 *   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
366 367 368
	 *	 '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
369 370 371 372 373
	 * );
	 * </pre>
	 *
	 * @return array list of related object declarations. Defaults to empty array.
	 */
Qiang Xue committed
374
	public static function relations()
w  
Qiang Xue committed
375 376 377 378
	{
		return array();
	}

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

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

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

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

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

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

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

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

	/**
	 * 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
595
		return array_keys($this->getMetaData()->table->columns);
w  
Qiang Xue committed
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610
	}

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

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

	/**
	 * Returns all column attribute values.
	 * Note, related objects are not returned.
Qiang Xue committed
629
	 * @param null|array $names names of attributes whose value needs to be returned.
w  
Qiang Xue committed
630 631 632 633 634
	 * 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
635
	public function getAttributes($names = null)
w  
Qiang Xue committed
636
	{
Qiang Xue committed
637 638
		if ($names === null) {
			$names = $this->attributeNames();
w  
Qiang Xue committed
639
		}
Qiang Xue committed
640 641 642 643 644 645 646 647 648 649 650 651 652 653
		$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
654
		if ($this->_oldAttributes === null) {
Qiang Xue committed
655 656 657 658 659 660 661 662 663 664
			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
665
			}
Qiang Xue committed
666
		}
Qiang Xue committed
667
		return $attributes;
w  
Qiang Xue committed
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 693 694
	}

	/**
	 * 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
695
		if (!$runValidation || $this->validate($attributes)) {
w  
Qiang Xue committed
696
			return $this->getIsNewRecord() ? $this->insert($attributes) : $this->update($attributes);
Qiang Xue committed
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 813 814
		}
		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
815
		} else {
w  
Qiang Xue committed
816
			return false;
Qiang Xue committed
817
		}
w  
Qiang Xue committed
818 819 820 821 822 823 824 825 826 827 828
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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