Model.php 25.2 KB
Newer Older
w  
Qiang Xue committed
1 2 3
<?php
/**
 * @link http://www.yiiframework.com/
Qiang Xue committed
4
 * @copyright Copyright (c) 2008 Yii Software LLC
w  
Qiang Xue committed
5 6 7
 * @license http://www.yiiframework.com/license/
 */

w  
Qiang Xue committed
8
namespace yii\base;
w  
Qiang Xue committed
9

10
use Yii;
11 12
use ArrayObject;
use ArrayIterator;
13
use yii\helpers\Inflector;
14
use yii\validators\RequiredValidator;
15
use yii\validators\Validator;
Qiang Xue committed
16

w  
Qiang Xue committed
17
/**
w  
Qiang Xue committed
18
 * Model is the base class for data models.
w  
Qiang Xue committed
19
 *
w  
Qiang Xue committed
20 21 22 23 24 25 26 27
 * Model implements the following commonly used features:
 *
 * - attribute declaration: by default, every public class member is considered as
 *   a model attribute
 * - attribute labels: each attribute may be associated with a label for display purpose
 * - massive attribute assignment
 * - scenario-based validation
 *
Qiang Xue committed
28
 * Model also raises the following events when performing data validation:
w  
Qiang Xue committed
29
 *
Qiang Xue committed
30 31
 * - [[EVENT_BEFORE_VALIDATE]]: an event raised at the beginning of [[validate()]]
 * - [[EVENT_AFTER_VALIDATE]]: an event raised at the end of [[validate()]]
w  
Qiang Xue committed
32 33 34
 *
 * You may directly use Model to store model data, or extend it with customization.
 * You may also customize Model by attaching [[ModelBehavior|model behaviors]].
w  
Qiang Xue committed
35
 *
36
 * @property ArrayObject $validators All the validators declared in the model.
Qiang Xue committed
37 38
 * @property array $activeValidators The validators applicable to the current [[scenario]].
 * @property array $errors Errors for all attributes or the specified attribute. Empty array is returned if no error.
resurtm committed
39
 * @property array $attributes Attribute values (name => value).
Qiang Xue committed
40 41
 * @property string $scenario The scenario that this model is in.
 *
w  
Qiang Xue committed
42
 * @author Qiang Xue <qiang.xue@gmail.com>
w  
Qiang Xue committed
43
 * @since 2.0
w  
Qiang Xue committed
44
 */
45
class Model extends Component implements \IteratorAggregate, \ArrayAccess
w  
Qiang Xue committed
46
{
47 48 49 50 51 52 53 54 55 56
	/**
	 * @event ModelEvent an event raised at the beginning of [[validate()]]. You may set
	 * [[ModelEvent::isValid]] to be false to stop the validation.
	 */
	const EVENT_BEFORE_VALIDATE = 'beforeValidate';
	/**
	 * @event Event an event raised at the end of [[validate()]]
	 */
	const EVENT_AFTER_VALIDATE = 'afterValidate';

Qiang Xue committed
57 58 59 60 61
	/**
	 * @var array validation errors (attribute name => array of errors)
	 */
	private $_errors;
	/**
62
	 * @var ArrayObject list of validators
Qiang Xue committed
63 64 65 66 67
	 */
	private $_validators;
	/**
	 * @var string current scenario
	 */
68
	private $_scenario = 'default';
w  
Qiang Xue committed
69 70 71 72

	/**
	 * Returns the validation rules for attributes.
	 *
Qiang Xue committed
73
	 * Validation rules are used by [[validate()]] to check if attribute values are valid.
w  
Qiang Xue committed
74 75
	 * Child classes may override this method to declare different validation rules.
	 *
w  
Qiang Xue committed
76
	 * Each rule is an array with the following structure:
w  
Qiang Xue committed
77
	 *
w  
Qiang Xue committed
78
	 * ~~~
w  
Qiang Xue committed
79
	 * array(
Qiang Xue committed
80 81
	 *     'attribute list',
	 *     'validator type',
resurtm committed
82
	 *     'on' => 'scenario name',
Qiang Xue committed
83
	 *     ...other parameters...
w  
Qiang Xue committed
84 85 86
	 * )
	 * ~~~
	 *
w  
Qiang Xue committed
87
	 * where
w  
Qiang Xue committed
88 89 90
	 *
	 *  - attribute list: required, specifies the attributes (separated by commas) to be validated;
	 *  - validator type: required, specifies the validator to be used. It can be the name of a model
Qiang Xue committed
91
	 *    class method, the name of a built-in validator, or a validator class name (or its path alias).
w  
Qiang Xue committed
92
	 *  - on: optional, specifies the [[scenario|scenarios]] (separated by commas) when the validation
Qiang Xue committed
93
	 *    rule can be applied. If this option is not set, the rule will apply to all scenarios.
w  
Qiang Xue committed
94
	 *  - additional name-value pairs can be specified to initialize the corresponding validator properties.
Qiang Xue committed
95
	 *    Please refer to individual validator class API for possible properties.
w  
Qiang Xue committed
96
	 *
Qiang Xue committed
97 98
	 * A validator can be either an object of a class extending [[Validator]], or a model class method
	 * (called *inline validator*) that has the following signature:
w  
Qiang Xue committed
99
	 *
w  
Qiang Xue committed
100
	 * ~~~
w  
Qiang Xue committed
101
	 * // $params refers to validation parameters given in the rule
w  
Qiang Xue committed
102 103 104
	 * function validatorName($attribute, $params)
	 * ~~~
	 *
Qiang Xue committed
105
	 * Yii also provides a set of [[Validator::builtInValidators|built-in validators]].
Qiang Xue committed
106
	 * They each has an alias name which can be used when specifying a validation rule.
w  
Qiang Xue committed
107
	 *
Qiang Xue committed
108
	 * Below are some examples:
w  
Qiang Xue committed
109
	 *
w  
Qiang Xue committed
110
	 * ~~~
w  
Qiang Xue committed
111
	 * array(
Qiang Xue committed
112 113 114
	 *     // built-in "required" validator
	 *     array('username', 'required'),
	 *     // built-in "length" validator customized with "min" and "max" properties
resurtm committed
115
	 *     array('username', 'length', 'min' => 3, 'max' => 12),
Qiang Xue committed
116
	 *     // built-in "compare" validator that is used in "register" scenario only
resurtm committed
117
	 *     array('password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'),
Qiang Xue committed
118
	 *     // an inline validator defined via the "authenticate()" method in the model class
resurtm committed
119
	 *     array('password', 'authenticate', 'on' => 'login'),
Qiang Xue committed
120 121
	 *     // a validator of class "CaptchaValidator"
	 *     array('captcha', 'CaptchaValidator'),
w  
Qiang Xue committed
122
	 * );
w  
Qiang Xue committed
123
	 * ~~~
w  
Qiang Xue committed
124 125
	 *
	 * Note, in order to inherit rules defined in the parent class, a child class needs to
w  
Qiang Xue committed
126
	 * merge the parent rules with child rules using functions such as `array_merge()`.
w  
Qiang Xue committed
127
	 *
w  
Qiang Xue committed
128
	 * @return array validation rules
129
	 * @see scenarios
w  
Qiang Xue committed
130 131 132 133 134 135
	 */
	public function rules()
	{
		return array();
	}

136
	/**
137
	 * Returns a list of scenarios and the corresponding active attributes.
Qiang Xue committed
138
	 * An active attribute is one that is subject to validation in the current scenario.
139 140 141 142 143 144 145 146 147 148
	 * The returned array should be in the following format:
	 *
	 * ~~~
	 * array(
	 *     'scenario1' => array('attribute11', 'attribute12', ...),
	 *     'scenario2' => array('attribute21', 'attribute22', ...),
	 *     ...
	 * )
	 * ~~~
	 *
Qiang Xue committed
149
	 * By default, an active attribute that is considered safe and can be massively assigned.
150
	 * If an attribute should NOT be massively assigned (thus considered unsafe),
Qiang Xue committed
151
	 * please prefix the attribute with an exclamation character (e.g. '!rank').
152
	 *
Qiang Xue committed
153 154 155 156 157
	 * The default implementation of this method will return a 'default' scenario
	 * which corresponds to all attributes listed in the validation rules applicable
	 * to the 'default' scenario.
	 *
	 * @return array a list of scenarios and the corresponding active attributes.
158 159 160
	 */
	public function scenarios()
	{
Qiang Xue committed
161 162
		$attributes = array();
		foreach ($this->getActiveValidators() as $validator) {
Qiang Xue committed
163 164 165 166
			if ($validator->isActive('default')) {
				foreach ($validator->attributes as $name) {
					$attributes[$name] = true;
				}
Qiang Xue committed
167 168 169 170 171
			}
		}
		return array(
			'default' => array_keys($attributes),
		);
172 173
	}

174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
	/**
	 * Returns the form name that this model class should use.
	 *
	 * The form name is mainly used by [[\yii\web\ActiveForm]] to determine how to name
	 * the input fields for the attributes in a model. If the form name is "A" and an attribute
	 * name is "b", then the corresponding input name would be "A[b]". If the form name is
	 * an empty string, then the input name would be "b".
	 *
	 * By default, this method returns the model class name (without the namespace part)
	 * as the form name. You may override it when the model is used in different forms.
	 *
	 * @return string the form name of this model class.
	 */
	public function formName()
	{
189 190
		$reflector = new \ReflectionClass($this);
		return $reflector->getShortName();
191 192
	}

193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
	/**
	 * Returns the list of attribute names.
	 * By default, this method returns all public non-static properties of the class.
	 * You may override this method to change the default behavior.
	 * @return array list of attribute names.
	 */
	public function attributes()
	{
		$class = new \ReflectionClass($this);
		$names = array();
		foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
			$name = $property->getName();
			if (!$property->isStatic()) {
				$names[] = $name;
			}
		}
Qiang Xue committed
209
		return $names;
210 211
	}

w  
Qiang Xue committed
212 213
	/**
	 * Returns the attribute labels.
w  
Qiang Xue committed
214 215 216 217 218
	 *
	 * Attribute labels are mainly used for display purpose. For example, given an attribute
	 * `firstName`, we can declare a label `First Name` which is more user-friendly and can
	 * be displayed to end users.
	 *
Qiang Xue committed
219
	 * By default an attribute label is generated using [[generateAttributeLabel()]].
w  
Qiang Xue committed
220 221 222
	 * This method allows you to explicitly specify attribute labels.
	 *
	 * Note, in order to inherit labels defined in the parent class, a child class needs to
w  
Qiang Xue committed
223
	 * merge the parent labels with child labels using functions such as `array_merge()`.
w  
Qiang Xue committed
224
	 *
resurtm committed
225
	 * @return array attribute labels (name => label)
w  
Qiang Xue committed
226 227 228 229 230 231 232 233
	 * @see generateAttributeLabel
	 */
	public function attributeLabels()
	{
		return array();
	}

	/**
w  
Qiang Xue committed
234
	 * Performs the data validation.
w  
Qiang Xue committed
235
	 *
236 237 238 239 240
	 * This method executes the validation rules applicable to the current [[scenario]].
	 * The following criteria are used to determine whether a rule is currently applicable:
	 *
	 * - the rule must be associated with the attributes relevant to the current scenario;
	 * - the rules must be effective for the current scenario.
w  
Qiang Xue committed
241
	 *
Qiang Xue committed
242
	 * This method will call [[beforeValidate()]] and [[afterValidate()]] before and
243 244
	 * after the actual validation, respectively. If [[beforeValidate()]] returns false,
	 * the validation will be cancelled and [[afterValidate()]] will not be called.
w  
Qiang Xue committed
245
	 *
246 247
	 * Errors found during the validation can be retrieved via [[getErrors()]]
	 * and [[getError()]].
w  
Qiang Xue committed
248
	 *
w  
Qiang Xue committed
249 250 251
	 * @param array $attributes list of attributes that should be validated.
	 * If this parameter is empty, it means any attribute listed in the applicable
	 * validation rules should be validated.
Qiang Xue committed
252
	 * @param boolean $clearErrors whether to call [[clearErrors()]] before performing validation
w  
Qiang Xue committed
253 254
	 * @return boolean whether the validation is successful without any error.
	 */
w  
Qiang Xue committed
255
	public function validate($attributes = null, $clearErrors = true)
w  
Qiang Xue committed
256
	{
w  
Qiang Xue committed
257
		if ($clearErrors) {
w  
Qiang Xue committed
258
			$this->clearErrors();
w  
Qiang Xue committed
259
		}
260 261 262
		if ($attributes === null) {
			$attributes = $this->activeAttributes();
		}
w  
Qiang Xue committed
263
		if ($this->beforeValidate()) {
w  
Qiang Xue committed
264
			foreach ($this->getActiveValidators() as $validator) {
w  
Qiang Xue committed
265 266
				$validator->validate($this, $attributes);
			}
w  
Qiang Xue committed
267 268 269
			$this->afterValidate();
			return !$this->hasErrors();
		}
w  
Qiang Xue committed
270
		return false;
w  
Qiang Xue committed
271 272 273 274
	}

	/**
	 * This method is invoked before validation starts.
Qiang Xue committed
275
	 * The default implementation raises a `beforeValidate` event.
w  
Qiang Xue committed
276 277
	 * You may override this method to do preliminary checks before validation.
	 * Make sure the parent implementation is invoked so that the event can be raised.
278
	 * @return boolean whether the validation should be executed. Defaults to true.
w  
Qiang Xue committed
279 280
	 * If false is returned, the validation will stop and the model is considered invalid.
	 */
w  
Qiang Xue committed
281
	public function beforeValidate()
w  
Qiang Xue committed
282
	{
Qiang Xue committed
283
		$event = new ModelEvent;
284
		$this->trigger(self::EVENT_BEFORE_VALIDATE, $event);
Qiang Xue committed
285
		return $event->isValid;
w  
Qiang Xue committed
286 287 288 289
	}

	/**
	 * This method is invoked after validation ends.
Qiang Xue committed
290
	 * The default implementation raises an `afterValidate` event.
w  
Qiang Xue committed
291 292 293
	 * You may override this method to do postprocessing after validation.
	 * Make sure the parent implementation is invoked so that the event can be raised.
	 */
w  
Qiang Xue committed
294
	public function afterValidate()
w  
Qiang Xue committed
295
	{
296
		$this->trigger(self::EVENT_AFTER_VALIDATE);
w  
Qiang Xue committed
297 298 299
	}

	/**
Qiang Xue committed
300
	 * Returns all the validators declared in [[rules()]].
w  
Qiang Xue committed
301
	 *
Qiang Xue committed
302
	 * This method differs from [[getActiveValidators()]] in that the latter
w  
Qiang Xue committed
303 304
	 * only returns the validators applicable to the current [[scenario]].
	 *
305
	 * Because this method returns an ArrayObject object, you may
w  
Qiang Xue committed
306 307 308
	 * manipulate it by inserting or removing validators (useful in model behaviors).
	 * For example,
	 *
w  
Qiang Xue committed
309
	 * ~~~
310
	 * $model->validators[] = $newValidator;
w  
Qiang Xue committed
311 312
	 * ~~~
	 *
313
	 * @return ArrayObject all the validators declared in the model.
w  
Qiang Xue committed
314
	 */
w  
Qiang Xue committed
315
	public function getValidators()
w  
Qiang Xue committed
316
	{
w  
Qiang Xue committed
317
		if ($this->_validators === null) {
w  
Qiang Xue committed
318
			$this->_validators = $this->createValidators();
w  
Qiang Xue committed
319
		}
w  
Qiang Xue committed
320 321 322 323
		return $this->_validators;
	}

	/**
w  
Qiang Xue committed
324 325
	 * Returns the validators applicable to the current [[scenario]].
	 * @param string $attribute the name of the attribute whose applicable validators should be returned.
w  
Qiang Xue committed
326
	 * If this is null, the validators for ALL attributes in the model will be returned.
Qiang Xue committed
327
	 * @return \yii\validators\Validator[] the validators applicable to the current [[scenario]].
w  
Qiang Xue committed
328
	 */
w  
Qiang Xue committed
329
	public function getActiveValidators($attribute = null)
w  
Qiang Xue committed
330
	{
w  
Qiang Xue committed
331 332
		$validators = array();
		$scenario = $this->getScenario();
333
		/** @var $validator Validator */
w  
Qiang Xue committed
334
		foreach ($this->getValidators() as $validator) {
Qiang Xue committed
335
			if ($validator->isActive($scenario) && ($attribute === null || in_array($attribute, $validator->attributes, true))) {
Qiang Xue committed
336
				$validators[] = $validator;
w  
Qiang Xue committed
337 338 339 340 341 342
			}
		}
		return $validators;
	}

	/**
Qiang Xue committed
343 344
	 * Creates validator objects based on the validation rules specified in [[rules()]].
	 * Unlike [[getValidators()]], each time this method is called, a new list of validators will be returned.
345
	 * @return ArrayObject validators
Qiang Xue committed
346
	 * @throws InvalidConfigException if any validation rule configuration is invalid
w  
Qiang Xue committed
347 348 349
	 */
	public function createValidators()
	{
350
		$validators = new ArrayObject;
w  
Qiang Xue committed
351
		foreach ($this->rules() as $rule) {
352
			if ($rule instanceof Validator) {
353
				$validators->append($rule);
354
			} elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
355
				$validator = Validator::createValidator($rule[1], $this, $rule[0], array_slice($rule, 2));
356
				$validators->append($validator);
Qiang Xue committed
357
			} else {
Qiang Xue committed
358
				throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
w  
Qiang Xue committed
359
			}
w  
Qiang Xue committed
360 361 362 363 364 365 366
		}
		return $validators;
	}

	/**
	 * Returns a value indicating whether the attribute is required.
	 * This is determined by checking if the attribute is associated with a
w  
Qiang Xue committed
367
	 * [[\yii\validators\RequiredValidator|required]] validation rule in the
w  
Qiang Xue committed
368
	 * current [[scenario]].
w  
Qiang Xue committed
369 370 371 372 373
	 * @param string $attribute attribute name
	 * @return boolean whether the attribute is required
	 */
	public function isAttributeRequired($attribute)
	{
w  
Qiang Xue committed
374
		foreach ($this->getActiveValidators($attribute) as $validator) {
375
			if ($validator instanceof RequiredValidator) {
w  
Qiang Xue committed
376
				return true;
w  
Qiang Xue committed
377
			}
w  
Qiang Xue committed
378 379 380 381 382 383 384 385 386 387 388
		}
		return false;
	}

	/**
	 * Returns a value indicating whether the attribute is safe for massive assignments.
	 * @param string $attribute attribute name
	 * @return boolean whether the attribute is safe for massive assignments
	 */
	public function isAttributeSafe($attribute)
	{
389
		return in_array($attribute, $this->safeAttributes(), true);
w  
Qiang Xue committed
390 391 392 393 394 395 396 397 398 399 400
	}

	/**
	 * Returns the text label for the specified attribute.
	 * @param string $attribute the attribute name
	 * @return string the attribute label
	 * @see generateAttributeLabel
	 * @see attributeLabels
	 */
	public function getAttributeLabel($attribute)
	{
w  
Qiang Xue committed
401
		$labels = $this->attributeLabels();
Alex committed
402
		return isset($labels[$attribute]) ? $labels[$attribute] : $this->generateAttributeLabel($attribute);
w  
Qiang Xue committed
403 404 405 406
	}

	/**
	 * Returns a value indicating whether there is any validation error.
407
	 * @param string|null $attribute attribute name. Use null to check all attributes.
w  
Qiang Xue committed
408 409
	 * @return boolean whether there is any error.
	 */
w  
Qiang Xue committed
410
	public function hasErrors($attribute = null)
w  
Qiang Xue committed
411
	{
w  
Qiang Xue committed
412
		return $attribute === null ? !empty($this->_errors) : isset($this->_errors[$attribute]);
w  
Qiang Xue committed
413 414 415 416 417 418
	}

	/**
	 * Returns the errors for all attribute or a single attribute.
	 * @param string $attribute attribute name. Use null to retrieve errors for all attributes.
	 * @return array errors for all attributes or the specified attribute. Empty array is returned if no error.
w  
Qiang Xue committed
419 420
	 * Note that when returning errors for all attributes, the result is a two-dimensional array, like the following:
	 *
w  
Qiang Xue committed
421
	 * ~~~
w  
Qiang Xue committed
422
	 * array(
Qiang Xue committed
423 424 425 426 427 428 429
	 *     'username' => array(
	 *         'Username is required.',
	 *         'Username must contain only word characters.',
	 *     ),
	 *     'email' => array(
	 *         'Email address is invalid.',
	 *     )
w  
Qiang Xue committed
430 431 432 433
	 * )
	 * ~~~
	 *
	 * @see getError
w  
Qiang Xue committed
434
	 */
w  
Qiang Xue committed
435
	public function getErrors($attribute = null)
w  
Qiang Xue committed
436
	{
w  
Qiang Xue committed
437
		if ($attribute === null) {
w  
Qiang Xue committed
438
			return $this->_errors === null ? array() : $this->_errors;
Qiang Xue committed
439
		} else {
w  
Qiang Xue committed
440
			return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : array();
w  
Qiang Xue committed
441
		}
w  
Qiang Xue committed
442 443
	}

Qiang Xue committed
444 445 446 447 448 449 450 451 452 453
	/**
	 * Returns the first error of every attribute in the model.
	 * @return array the first errors. An empty array will be returned if there is no error.
	 */
	public function getFirstErrors()
	{
		if (empty($this->_errors)) {
			return array();
		} else {
			$errors = array();
454 455 456
			foreach ($this->_errors as $attributeErrors) {
				if (isset($attributeErrors[0])) {
					$errors[] = $attributeErrors[0];
Qiang Xue committed
457 458 459 460 461 462
				}
			}
		}
		return $errors;
	}

w  
Qiang Xue committed
463 464 465 466
	/**
	 * Returns the first error of the specified attribute.
	 * @param string $attribute attribute name.
	 * @return string the error message. Null is returned if no error.
w  
Qiang Xue committed
467
	 * @see getErrors
w  
Qiang Xue committed
468
	 */
Qiang Xue committed
469
	public function getFirstError($attribute)
w  
Qiang Xue committed
470 471 472 473 474 475 476 477 478
	{
		return isset($this->_errors[$attribute]) ? reset($this->_errors[$attribute]) : null;
	}

	/**
	 * Adds a new error to the specified attribute.
	 * @param string $attribute attribute name
	 * @param string $error new error message
	 */
479
	public function addError($attribute, $error = '')
w  
Qiang Xue committed
480
	{
w  
Qiang Xue committed
481
		$this->_errors[$attribute][] = $error;
w  
Qiang Xue committed
482 483 484 485 486 487
	}

	/**
	 * Removes errors for all attributes or a single attribute.
	 * @param string $attribute attribute name. Use null to remove errors for all attribute.
	 */
w  
Qiang Xue committed
488
	public function clearErrors($attribute = null)
w  
Qiang Xue committed
489
	{
w  
Qiang Xue committed
490
		if ($attribute === null) {
w  
Qiang Xue committed
491
			$this->_errors = array();
Qiang Xue committed
492
		} else {
w  
Qiang Xue committed
493
			unset($this->_errors[$attribute]);
w  
Qiang Xue committed
494
		}
w  
Qiang Xue committed
495 496 497
	}

	/**
w  
Qiang Xue committed
498 499
	 * Generates a user friendly attribute label based on the give attribute name.
	 * This is done by replacing underscores, dashes and dots with blanks and
w  
Qiang Xue committed
500
	 * changing the first letter of each word to upper case.
w  
Qiang Xue committed
501
	 * For example, 'department_name' or 'DepartmentName' will generate 'Department Name'.
w  
Qiang Xue committed
502 503 504 505 506
	 * @param string $name the column name
	 * @return string the attribute label
	 */
	public function generateAttributeLabel($name)
	{
507
		return Inflector::camel2words($name, true);
w  
Qiang Xue committed
508 509 510
	}

	/**
w  
Qiang Xue committed
511
	 * Returns attribute values.
w  
Qiang Xue committed
512
	 * @param array $names list of attributes whose value needs to be returned.
513
	 * Defaults to null, meaning all attributes listed in [[attributes()]] will be returned.
w  
Qiang Xue committed
514
	 * If it is an array, only the attributes in the array will be returned.
515
	 * @param array $except list of attributes whose value should NOT be returned.
resurtm committed
516
	 * @return array attribute values (name => value).
w  
Qiang Xue committed
517
	 */
518
	public function getAttributes($names = null, $except = array())
w  
Qiang Xue committed
519
	{
w  
Qiang Xue committed
520
		$values = array();
521 522 523 524 525 526 527 528
		if ($names === null) {
			$names = $this->attributes();
		}
		foreach ($names as $name) {
			$values[$name] = $this->$name;
		}
		foreach ($except as $name) {
			unset($values[$name]);
w  
Qiang Xue committed
529 530 531
		}

		return $values;
w  
Qiang Xue committed
532 533 534 535
	}

	/**
	 * Sets the attribute values in a massive way.
resurtm committed
536
	 * @param array $values attribute values (name => value) to be assigned to the model.
w  
Qiang Xue committed
537
	 * @param boolean $safeOnly whether the assignments should only be done to the safe attributes.
w  
Qiang Xue committed
538
	 * A safe attribute is one that is associated with a validation rule in the current [[scenario]].
539 540
	 * @see safeAttributes()
	 * @see attributes()
w  
Qiang Xue committed
541
	 */
w  
Qiang Xue committed
542
	public function setAttributes($values, $safeOnly = true)
w  
Qiang Xue committed
543
	{
w  
Qiang Xue committed
544
		if (is_array($values)) {
545
			$attributes = array_flip($safeOnly ? $this->safeAttributes() : $this->attributes());
w  
Qiang Xue committed
546 547 548
			foreach ($values as $name => $value) {
				if (isset($attributes[$name])) {
					$this->$name = $value;
Qiang Xue committed
549
				} elseif ($safeOnly) {
w  
Qiang Xue committed
550 551 552
					$this->onUnsafeAttribute($name, $value);
				}
			}
w  
Qiang Xue committed
553 554 555 556 557 558 559 560 561 562
		}
	}

	/**
	 * This method is invoked when an unsafe attribute is being massively assigned.
	 * The default implementation will log a warning message if YII_DEBUG is on.
	 * It does nothing otherwise.
	 * @param string $name the unsafe attribute name
	 * @param mixed $value the attribute value
	 */
w  
Qiang Xue committed
563
	public function onUnsafeAttribute($name, $value)
w  
Qiang Xue committed
564
	{
w  
Qiang Xue committed
565
		if (YII_DEBUG) {
566
			\Yii::info("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __METHOD__);
w  
Qiang Xue committed
567
		}
w  
Qiang Xue committed
568 569 570 571 572 573 574 575
	}

	/**
	 * Returns the scenario that this model is used in.
	 *
	 * Scenario affects how validation is performed and which attributes can
	 * be massively assigned.
	 *
576
	 * @return string the scenario that this model is in. Defaults to 'default'.
w  
Qiang Xue committed
577 578 579 580 581 582 583 584 585 586 587 588 589
	 */
	public function getScenario()
	{
		return $this->_scenario;
	}

	/**
	 * Sets the scenario for the model.
	 * @param string $value the scenario that this model is in.
	 * @see getScenario
	 */
	public function setScenario($value)
	{
w  
Qiang Xue committed
590
		$this->_scenario = $value;
w  
Qiang Xue committed
591 592 593
	}

	/**
594
	 * Returns the attribute names that are safe to be massively assigned in the current scenario.
595
	 * @return string[] safe attribute names
w  
Qiang Xue committed
596
	 */
597
	public function safeAttributes()
w  
Qiang Xue committed
598
	{
599
		$scenario = $this->getScenario();
600
		$scenarios = $this->scenarios();
601 602 603
		if (!isset($scenarios[$scenario])) {
			return array();
		}
Qiang Xue committed
604
		$attributes = array();
605 606 607 608 609 610
		if (isset($scenarios[$scenario]['attributes']) && is_array($scenarios[$scenario]['attributes'])) {
			$scenarios[$scenario] = $scenarios[$scenario]['attributes'];
		}
		foreach ($scenarios[$scenario] as $attribute) {
			if ($attribute[0] !== '!') {
				$attributes[] = $attribute;
w  
Qiang Xue committed
611 612
			}
		}
Qiang Xue committed
613
		return $attributes;
614
	}
w  
Qiang Xue committed
615

616 617
	/**
	 * Returns the attribute names that are subject to validation in the current scenario.
618
	 * @return string[] safe attribute names
619 620 621
	 */
	public function activeAttributes()
	{
622
		$scenario = $this->getScenario();
623
		$scenarios = $this->scenarios();
624
		if (!isset($scenarios[$scenario])) {
Qiang Xue committed
625
			return array();
w  
Qiang Xue committed
626
		}
627 628 629 630 631 632 633 634 635 636 637
		if (isset($scenarios[$scenario]['attributes']) && is_array($scenarios[$scenario]['attributes'])) {
			$attributes = $scenarios[$scenario]['attributes'];
		} else {
			$attributes = $scenarios[$scenario];
		}
		foreach ($attributes as $i => $attribute) {
			if ($attribute[0] === '!') {
				$attributes[$i] = substr($attribute, 1);
			}
		}
		return $attributes;
w  
Qiang Xue committed
638 639
	}

640 641
	/**
	 * Populates the model with the data from end user.
642 643 644
	 * The data to be loaded is `$data[formName]`, where `formName` refers to the value of [[formName()]].
	 * If [[formName()]] is empty, the whole `$data` array will be used to populate the model.
	 * The data being populated is subject to the safety check by [[setAttributes()]].
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
	 * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array
	 * supplied by end user.
	 * @return boolean whether the model is successfully populated with some data.
	 */
	public function load($data)
	{
		$scope = $this->formName();
		if ($scope == '') {
			$this->setAttributes($data);
			return true;
		} elseif (isset($data[$scope])) {
			$this->setAttributes($data[$scope]);
			return true;
		} else {
			return false;
		}
	}

663 664 665 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 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
	/**
	 * Populates a set of models with the data from end user.
	 * This method is mainly used to collect tabular data input.
	 * The data to be loaded for each model is `$data[formName][index]`, where `formName`
	 * refers to the value of [[formName()]], and `index` the index of the model in the `$models` array.
	 * If [[formName()]] is empty, `$data[index]` will be used to populate each model.
	 * The data being populated to each model is subject to the safety check by [[setAttributes()]].
	 * @param array $models the models to be populated. Note that all models should have the same class.
	 * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array
	 * supplied by end user.
	 * @return boolean whether the model is successfully populated with some data.
	 */
	public static function loadMultiple($models, $data)
	{
		/** @var Model $model */
		$model = reset($models);
		if ($model === false) {
			return false;
		}
		$success = false;
		$scope = $model->formName();
		foreach ($models as $i => $model) {
			if ($scope == '') {
				if (isset($data[$i])) {
					$model->setAttributes($data[$i]);
					$success = true;
				}
			} elseif (isset($data[$scope][$i])) {
				$model->setAttributes($data[$scope[$i]]);
				$success = true;
			}
		}
		return $success;
	}

	/**
	 * Validates multiple models.
	 * @param array $models the models to be validated
	 * @return boolean whether all models are valid. False will be returned if one
	 * or multiple models have validation error.
	 */
	public static function validateMultiple($models)
	{
		$valid = true;
		/** @var Model $model */
		foreach ($models as $model) {
			$valid = $model->validate() && $valid;
		}
		return $valid;
	}

Qiang Xue committed
714
	/**
715
	 * Converts the object into an array.
Qiang Xue committed
716
	 * The default implementation will return [[attributes]].
717
	 * @return array the array representation of the object
Qiang Xue committed
718
	 */
719
	public function toArray()
Qiang Xue committed
720
	{
721
		return $this->getAttributes();
Qiang Xue committed
722 723
	}

w  
Qiang Xue committed
724 725 726
	/**
	 * Returns an iterator for traversing the attributes in the model.
	 * This method is required by the interface IteratorAggregate.
727
	 * @return ArrayIterator an iterator for traversing the items in the list.
w  
Qiang Xue committed
728 729 730
	 */
	public function getIterator()
	{
w  
Qiang Xue committed
731
		$attributes = $this->getAttributes();
732
		return new ArrayIterator($attributes);
w  
Qiang Xue committed
733 734 735 736
	}

	/**
	 * Returns whether there is an element at the specified offset.
w  
Qiang Xue committed
737 738
	 * This method is required by the SPL interface `ArrayAccess`.
	 * It is implicitly called when you use something like `isset($model[$offset])`.
w  
Qiang Xue committed
739 740 741 742 743
	 * @param mixed $offset the offset to check on
	 * @return boolean
	 */
	public function offsetExists($offset)
	{
Qiang Xue committed
744
		return $this->$offset !== null;
w  
Qiang Xue committed
745 746 747 748
	}

	/**
	 * Returns the element at the specified offset.
w  
Qiang Xue committed
749 750
	 * This method is required by the SPL interface `ArrayAccess`.
	 * It is implicitly called when you use something like `$value = $model[$offset];`.
w  
Qiang Xue committed
751
	 * @param mixed $offset the offset to retrieve element.
w  
Qiang Xue committed
752 753 754 755 756 757 758 759 760
	 * @return mixed the element at the offset, null if no element is found at the offset
	 */
	public function offsetGet($offset)
	{
		return $this->$offset;
	}

	/**
	 * Sets the element at the specified offset.
w  
Qiang Xue committed
761 762
	 * This method is required by the SPL interface `ArrayAccess`.
	 * It is implicitly called when you use something like `$model[$offset] = $item;`.
w  
Qiang Xue committed
763 764 765
	 * @param integer $offset the offset to set element
	 * @param mixed $item the element value
	 */
w  
Qiang Xue committed
766
	public function offsetSet($offset, $item)
w  
Qiang Xue committed
767
	{
w  
Qiang Xue committed
768
		$this->$offset = $item;
w  
Qiang Xue committed
769 770 771
	}

	/**
772
	 * Sets the element value at the specified offset to null.
w  
Qiang Xue committed
773 774
	 * This method is required by the SPL interface `ArrayAccess`.
	 * It is implicitly called when you use something like `unset($model[$offset])`.
w  
Qiang Xue committed
775 776 777 778
	 * @param mixed $offset the offset to unset element
	 */
	public function offsetUnset($offset)
	{
779
		$this->$offset = null;
w  
Qiang Xue committed
780 781
	}
}