Model.php 31.3 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
use ArrayAccess;
12 13
use ArrayObject;
use ArrayIterator;
14 15
use ReflectionClass;
use IteratorAggregate;
16
use yii\helpers\Inflector;
17
use yii\validators\RequiredValidator;
18
use yii\validators\Validator;
Qiang Xue committed
19

w  
Qiang Xue committed
20
/**
w  
Qiang Xue committed
21
 * Model is the base class for data models.
w  
Qiang Xue committed
22
 *
w  
Qiang Xue committed
23 24 25 26 27 28 29 30
 * 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
31
 * Model also raises the following events when performing data validation:
w  
Qiang Xue committed
32
 *
Qiang Xue committed
33 34
 * - [[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
35 36 37
 *
 * 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
38
 *
39 40
 * @property \yii\validators\Validator[] $activeValidators The validators applicable to the current
 * [[scenario]]. This property is read-only.
resurtm committed
41
 * @property array $attributes Attribute values (name => value).
42 43 44 45 46 47
 * @property array $errors An array of errors for all attributes. Empty array is returned if no error. The
 * result is a two-dimensional array. See [[getErrors()]] for detailed description. This property is read-only.
 * @property array $firstErrors The first errors. An empty array will be returned if there is no error. This
 * property is read-only.
 * @property ArrayIterator $iterator An iterator for traversing the items in the list. This property is
 * read-only.
48
 * @property string $scenario The scenario that this model is in. Defaults to [[SCENARIO_DEFAULT]].
49 50
 * @property ArrayObject|\yii\validators\Validator[] $validators All the validators declared in the model.
 * This property is read-only.
Qiang Xue committed
51
 *
w  
Qiang Xue committed
52
 * @author Qiang Xue <qiang.xue@gmail.com>
w  
Qiang Xue committed
53
 * @since 2.0
w  
Qiang Xue committed
54
 */
55
class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayable
w  
Qiang Xue committed
56
{
Qiang Xue committed
57 58
	use ArrayableTrait;

59 60 61
	/**
	 * The name of the default scenario.
	 */
62
	const SCENARIO_DEFAULT = 'default';
63 64 65 66 67 68 69 70 71 72
	/**
	 * @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
73 74 75 76 77
	/**
	 * @var array validation errors (attribute name => array of errors)
	 */
	private $_errors;
	/**
78
	 * @var ArrayObject list of validators
Qiang Xue committed
79 80 81 82 83
	 */
	private $_validators;
	/**
	 * @var string current scenario
	 */
84
	private $_scenario = self::SCENARIO_DEFAULT;
w  
Qiang Xue committed
85 86 87 88

	/**
	 * Returns the validation rules for attributes.
	 *
Qiang Xue committed
89
	 * Validation rules are used by [[validate()]] to check if attribute values are valid.
w  
Qiang Xue committed
90 91
	 * Child classes may override this method to declare different validation rules.
	 *
w  
Qiang Xue committed
92
	 * Each rule is an array with the following structure:
w  
Qiang Xue committed
93
	 *
w  
Qiang Xue committed
94
	 * ~~~
Alexander Makarov committed
95
	 * [
96
	 *     ['attribute1', 'attribute2'],
Qiang Xue committed
97
	 *     'validator type',
98
	 *     'on' => ['scenario1', 'scenario2'],
Qiang Xue committed
99
	 *     ...other parameters...
Alexander Makarov committed
100
	 * ]
w  
Qiang Xue committed
101 102
	 * ~~~
	 *
w  
Qiang Xue committed
103
	 * where
w  
Qiang Xue committed
104
	 *
slavcodev committed
105
	 *  - attribute list: required, specifies the attributes array to be validated, for single attribute you can pass string;
106 107
	 *  - validator type: required, specifies the validator to be used. It can be a built-in validator name,
	 *    a method name of the model class, an anonymous function, or a validator class name.
108
	 *  - on: optional, specifies the [[scenario|scenarios]] array when the validation
Qiang Xue committed
109
	 *    rule can be applied. If this option is not set, the rule will apply to all scenarios.
w  
Qiang Xue committed
110
	 *  - additional name-value pairs can be specified to initialize the corresponding validator properties.
Qiang Xue committed
111
	 *    Please refer to individual validator class API for possible properties.
w  
Qiang Xue committed
112
	 *
Qiang Xue committed
113 114
	 * 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
115
	 *
w  
Qiang Xue committed
116
	 * ~~~
w  
Qiang Xue committed
117
	 * // $params refers to validation parameters given in the rule
w  
Qiang Xue committed
118 119 120
	 * function validatorName($attribute, $params)
	 * ~~~
	 *
121
	 * In the above `$attribute` refers to currently validated attribute name while `$params` contains an array of
Alexander Makarov committed
122
	 * validator configuration options such as `max` in case of `string` validator. Currently validate attribute value
123 124
	 * can be accessed as `$this->[$attribute]`.
	 *
Qiang Xue committed
125
	 * Yii also provides a set of [[Validator::builtInValidators|built-in validators]].
Qiang Xue committed
126
	 * They each has an alias name which can be used when specifying a validation rule.
w  
Qiang Xue committed
127
	 *
Qiang Xue committed
128
	 * Below are some examples:
w  
Qiang Xue committed
129
	 *
w  
Qiang Xue committed
130
	 * ~~~
Alexander Makarov committed
131
	 * [
Qiang Xue committed
132
	 *     // built-in "required" validator
slavcodev committed
133
	 *     [['username', 'password'], 'required'],
Alexander Makarov committed
134
	 *     // built-in "string" validator customized with "min" and "max" properties
Alexander Makarov committed
135
	 *     ['username', 'string', 'min' => 3, 'max' => 12],
Qiang Xue committed
136
	 *     // built-in "compare" validator that is used in "register" scenario only
Alexander Makarov committed
137
	 *     ['password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'],
Qiang Xue committed
138
	 *     // an inline validator defined via the "authenticate()" method in the model class
Alexander Makarov committed
139
	 *     ['password', 'authenticate', 'on' => 'login'],
Qiang Xue committed
140
	 *     // a validator of class "DateRangeValidator"
Alexander Makarov committed
141 142
	 *     ['dateRange', 'DateRangeValidator'],
	 * ];
w  
Qiang Xue committed
143
	 * ~~~
w  
Qiang Xue committed
144 145
	 *
	 * Note, in order to inherit rules defined in the parent class, a child class needs to
w  
Qiang Xue committed
146
	 * merge the parent rules with child rules using functions such as `array_merge()`.
w  
Qiang Xue committed
147
	 *
w  
Qiang Xue committed
148
	 * @return array validation rules
149
	 * @see scenarios()
w  
Qiang Xue committed
150 151 152
	 */
	public function rules()
	{
Alexander Makarov committed
153
		return [];
w  
Qiang Xue committed
154 155
	}

156
	/**
157
	 * Returns a list of scenarios and the corresponding active attributes.
Qiang Xue committed
158
	 * An active attribute is one that is subject to validation in the current scenario.
159 160 161
	 * The returned array should be in the following format:
	 *
	 * ~~~
Alexander Makarov committed
162 163 164
	 * [
	 *     'scenario1' => ['attribute11', 'attribute12', ...],
	 *     'scenario2' => ['attribute21', 'attribute22', ...],
165
	 *     ...
Alexander Makarov committed
166
	 * ]
167 168
	 * ~~~
	 *
169
	 * By default, an active attribute is considered safe and can be massively assigned.
170
	 * If an attribute should NOT be massively assigned (thus considered unsafe),
Qiang Xue committed
171
	 * please prefix the attribute with an exclamation character (e.g. '!rank').
172
	 *
Qiang Xue committed
173
	 * The default implementation of this method will return all scenarios found in the [[rules()]]
174
	 * declaration. A special scenario named [[SCENARIO_DEFAULT]] will contain all attributes
175 176
	 * found in the [[rules()]]. Each scenario will be associated with the attributes that
	 * are being validated by the validation rules that apply to the scenario.
Qiang Xue committed
177 178
	 *
	 * @return array a list of scenarios and the corresponding active attributes.
179 180 181
	 */
	public function scenarios()
	{
182
		$scenarios = [self::SCENARIO_DEFAULT => []];
183
		foreach ($this->getValidators() as $validator) {
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
			foreach ($validator->on as $scenario) {
				$scenarios[$scenario] = [];
			}
			foreach ($validator->except as $scenario) {
				$scenarios[$scenario] = [];
			}
		}
		$names = array_keys($scenarios);

		foreach ($this->getValidators() as $validator) {
			if (empty($validator->on) && empty($validator->except)) {
				foreach ($names as $name) {
					foreach ($validator->attributes as $attribute) {
						$scenarios[$name][$attribute] = true;
					}
				}
			} elseif (empty($validator->on)) {
				foreach ($names as $name) {
					if (!in_array($name, $validator->except, true)) {
						foreach ($validator->attributes as $attribute) {
							$scenarios[$name][$attribute] = true;
						}
					}
207 208
				}
			} else {
209
				foreach ($validator->on as $name) {
210
					foreach ($validator->attributes as $attribute) {
211
						$scenarios[$name][$attribute] = true;
212 213 214 215
					}
				}
			}
		}
216

217
		foreach ($scenarios as $scenario => $attributes) {
218
			if (empty($attributes) && $scenario !== self::SCENARIO_DEFAULT) {
219 220 221
				unset($scenarios[$scenario]);
			} else {
				$scenarios[$scenario] = array_keys($attributes);
Qiang Xue committed
222 223
			}
		}
Qiang Xue committed
224

225
		return $scenarios;
226 227
	}

228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
	/**
	 * 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()
	{
243
		$reflector = new ReflectionClass($this);
244
		return $reflector->getShortName();
245 246
	}

247 248 249 250 251 252
	/**
	 * 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.
	 */
253
	public function attributes()
254
	{
255
		$class = new ReflectionClass($this);
Alexander Makarov committed
256
		$names = [];
257 258
		foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
			if (!$property->isStatic()) {
259
				$names[] = $property->getName();
260 261
			}
		}
Qiang Xue committed
262
		return $names;
263 264
	}

w  
Qiang Xue committed
265 266
	/**
	 * Returns the attribute labels.
w  
Qiang Xue committed
267 268 269 270 271
	 *
	 * 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
272
	 * By default an attribute label is generated using [[generateAttributeLabel()]].
w  
Qiang Xue committed
273 274 275
	 * 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
276
	 * merge the parent labels with child labels using functions such as `array_merge()`.
w  
Qiang Xue committed
277
	 *
resurtm committed
278
	 * @return array attribute labels (name => label)
279
	 * @see generateAttributeLabel()
w  
Qiang Xue committed
280 281 282
	 */
	public function attributeLabels()
	{
Alexander Makarov committed
283
		return [];
w  
Qiang Xue committed
284 285 286
	}

	/**
w  
Qiang Xue committed
287
	 * Performs the data validation.
w  
Qiang Xue committed
288
	 *
289 290 291 292 293
	 * 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
294
	 *
Qiang Xue committed
295
	 * This method will call [[beforeValidate()]] and [[afterValidate()]] before and
296 297
	 * after the actual validation, respectively. If [[beforeValidate()]] returns false,
	 * the validation will be cancelled and [[afterValidate()]] will not be called.
w  
Qiang Xue committed
298
	 *
299 300
	 * Errors found during the validation can be retrieved via [[getErrors()]],
	 * [[getFirstErrors()]] and [[getFirstError()]].
w  
Qiang Xue committed
301
	 *
w  
Qiang Xue committed
302 303 304
	 * @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
305
	 * @param boolean $clearErrors whether to call [[clearErrors()]] before performing validation
w  
Qiang Xue committed
306
	 * @return boolean whether the validation is successful without any error.
307
	 * @throws InvalidParamException if the current scenario is unknown.
w  
Qiang Xue committed
308
	 */
w  
Qiang Xue committed
309
	public function validate($attributes = null, $clearErrors = true)
w  
Qiang Xue committed
310
	{
311 312 313 314 315 316
		$scenarios = $this->scenarios();
		$scenario = $this->getScenario();
		if (!isset($scenarios[$scenario])) {
			throw new InvalidParamException("Unknown scenario: $scenario");
		}

w  
Qiang Xue committed
317
		if ($clearErrors) {
w  
Qiang Xue committed
318
			$this->clearErrors();
w  
Qiang Xue committed
319
		}
320 321 322
		if ($attributes === null) {
			$attributes = $this->activeAttributes();
		}
w  
Qiang Xue committed
323
		if ($this->beforeValidate()) {
w  
Qiang Xue committed
324
			foreach ($this->getActiveValidators() as $validator) {
Qiang Xue committed
325
				$validator->validateAttributes($this, $attributes);
w  
Qiang Xue committed
326
			}
w  
Qiang Xue committed
327 328 329
			$this->afterValidate();
			return !$this->hasErrors();
		}
w  
Qiang Xue committed
330
		return false;
w  
Qiang Xue committed
331 332 333 334
	}

	/**
	 * This method is invoked before validation starts.
Qiang Xue committed
335
	 * The default implementation raises a `beforeValidate` event.
w  
Qiang Xue committed
336 337
	 * 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.
338
	 * @return boolean whether the validation should be executed. Defaults to true.
w  
Qiang Xue committed
339 340
	 * If false is returned, the validation will stop and the model is considered invalid.
	 */
w  
Qiang Xue committed
341
	public function beforeValidate()
w  
Qiang Xue committed
342
	{
Qiang Xue committed
343
		$event = new ModelEvent;
344
		$this->trigger(self::EVENT_BEFORE_VALIDATE, $event);
Qiang Xue committed
345
		return $event->isValid;
w  
Qiang Xue committed
346 347 348 349
	}

	/**
	 * This method is invoked after validation ends.
Qiang Xue committed
350
	 * The default implementation raises an `afterValidate` event.
w  
Qiang Xue committed
351 352 353
	 * 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
354
	public function afterValidate()
w  
Qiang Xue committed
355
	{
356
		$this->trigger(self::EVENT_AFTER_VALIDATE);
w  
Qiang Xue committed
357 358 359
	}

	/**
Qiang Xue committed
360
	 * Returns all the validators declared in [[rules()]].
w  
Qiang Xue committed
361
	 *
Qiang Xue committed
362
	 * This method differs from [[getActiveValidators()]] in that the latter
w  
Qiang Xue committed
363 364
	 * only returns the validators applicable to the current [[scenario]].
	 *
365
	 * Because this method returns an ArrayObject object, you may
w  
Qiang Xue committed
366 367 368
	 * manipulate it by inserting or removing validators (useful in model behaviors).
	 * For example,
	 *
w  
Qiang Xue committed
369
	 * ~~~
370
	 * $model->validators[] = $newValidator;
w  
Qiang Xue committed
371 372
	 * ~~~
	 *
slavcodev committed
373
	 * @return ArrayObject|\yii\validators\Validator[] all the validators declared in the model.
w  
Qiang Xue committed
374
	 */
w  
Qiang Xue committed
375
	public function getValidators()
w  
Qiang Xue committed
376
	{
w  
Qiang Xue committed
377
		if ($this->_validators === null) {
w  
Qiang Xue committed
378
			$this->_validators = $this->createValidators();
w  
Qiang Xue committed
379
		}
w  
Qiang Xue committed
380 381 382 383
		return $this->_validators;
	}

	/**
w  
Qiang Xue committed
384 385
	 * 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
386
	 * If this is null, the validators for ALL attributes in the model will be returned.
Qiang Xue committed
387
	 * @return \yii\validators\Validator[] the validators applicable to the current [[scenario]].
w  
Qiang Xue committed
388
	 */
w  
Qiang Xue committed
389
	public function getActiveValidators($attribute = null)
w  
Qiang Xue committed
390
	{
Alexander Makarov committed
391
		$validators = [];
w  
Qiang Xue committed
392
		$scenario = $this->getScenario();
w  
Qiang Xue committed
393
		foreach ($this->getValidators() as $validator) {
Qiang Xue committed
394
			if ($validator->isActive($scenario) && ($attribute === null || in_array($attribute, $validator->attributes, true))) {
Qiang Xue committed
395
				$validators[] = $validator;
w  
Qiang Xue committed
396 397 398 399 400 401
			}
		}
		return $validators;
	}

	/**
Qiang Xue committed
402 403
	 * 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.
404
	 * @return ArrayObject validators
Qiang Xue committed
405
	 * @throws InvalidConfigException if any validation rule configuration is invalid
w  
Qiang Xue committed
406 407 408
	 */
	public function createValidators()
	{
409
		$validators = new ArrayObject;
w  
Qiang Xue committed
410
		foreach ($this->rules() as $rule) {
411
			if ($rule instanceof Validator) {
412
				$validators->append($rule);
413
			} elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
414
				$validator = Validator::createValidator($rule[1], $this, (array) $rule[0], array_slice($rule, 2));
415
				$validators->append($validator);
Qiang Xue committed
416
			} else {
Qiang Xue committed
417
				throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
w  
Qiang Xue committed
418
			}
w  
Qiang Xue committed
419 420 421 422 423 424 425
		}
		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
426
	 * [[\yii\validators\RequiredValidator|required]] validation rule in the
w  
Qiang Xue committed
427
	 * current [[scenario]].
w  
Qiang Xue committed
428 429 430 431 432
	 * @param string $attribute attribute name
	 * @return boolean whether the attribute is required
	 */
	public function isAttributeRequired($attribute)
	{
w  
Qiang Xue committed
433
		foreach ($this->getActiveValidators($attribute) as $validator) {
434
			if ($validator instanceof RequiredValidator) {
w  
Qiang Xue committed
435
				return true;
w  
Qiang Xue committed
436
			}
w  
Qiang Xue committed
437 438 439 440 441 442 443 444
		}
		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
445
	 * @see safeAttributes()
w  
Qiang Xue committed
446 447 448
	 */
	public function isAttributeSafe($attribute)
	{
449
		return in_array($attribute, $this->safeAttributes(), true);
w  
Qiang Xue committed
450 451
	}

452 453 454 455 456 457 458 459 460 461 462
	/**
	 * Returns a value indicating whether the attribute is active in the current scenario.
	 * @param string $attribute attribute name
	 * @return boolean whether the attribute is active in the current scenario
	 * @see activeAttributes()
	 */
	public function isAttributeActive($attribute)
	{
		return in_array($attribute, $this->activeAttributes(), true);
	}

w  
Qiang Xue committed
463 464 465 466
	/**
	 * Returns the text label for the specified attribute.
	 * @param string $attribute the attribute name
	 * @return string the attribute label
467 468
	 * @see generateAttributeLabel()
	 * @see attributeLabels()
w  
Qiang Xue committed
469 470 471
	 */
	public function getAttributeLabel($attribute)
	{
w  
Qiang Xue committed
472
		$labels = $this->attributeLabels();
Alex committed
473
		return isset($labels[$attribute]) ? $labels[$attribute] : $this->generateAttributeLabel($attribute);
w  
Qiang Xue committed
474 475 476 477
	}

	/**
	 * Returns a value indicating whether there is any validation error.
478
	 * @param string|null $attribute attribute name. Use null to check all attributes.
w  
Qiang Xue committed
479 480
	 * @return boolean whether there is any error.
	 */
w  
Qiang Xue committed
481
	public function hasErrors($attribute = null)
w  
Qiang Xue committed
482
	{
w  
Qiang Xue committed
483
		return $attribute === null ? !empty($this->_errors) : isset($this->_errors[$attribute]);
w  
Qiang Xue committed
484 485 486 487 488
	}

	/**
	 * Returns the errors for all attribute or a single attribute.
	 * @param string $attribute attribute name. Use null to retrieve errors for all attributes.
489 490
	 * @property array An array of errors for all attributes. Empty array is returned if no error.
	 * The result is a two-dimensional array. See [[getErrors()]] for detailed description.
w  
Qiang Xue committed
491
	 * @return array errors for all attributes or the specified attribute. Empty array is returned if no error.
w  
Qiang Xue committed
492 493
	 * Note that when returning errors for all attributes, the result is a two-dimensional array, like the following:
	 *
w  
Qiang Xue committed
494
	 * ~~~
Alexander Makarov committed
495 496
	 * [
	 *     'username' => [
Qiang Xue committed
497 498
	 *         'Username is required.',
	 *         'Username must contain only word characters.',
Alexander Makarov committed
499 500
	 *     ],
	 *     'email' => [
Qiang Xue committed
501
	 *         'Email address is invalid.',
Alexander Makarov committed
502 503
	 *     ]
	 * ]
w  
Qiang Xue committed
504 505
	 * ~~~
	 *
506 507
	 * @see getFirstErrors()
	 * @see getFirstError()
w  
Qiang Xue committed
508
	 */
w  
Qiang Xue committed
509
	public function getErrors($attribute = null)
w  
Qiang Xue committed
510
	{
w  
Qiang Xue committed
511
		if ($attribute === null) {
Alexander Makarov committed
512
			return $this->_errors === null ? [] : $this->_errors;
Qiang Xue committed
513
		} else {
Alexander Makarov committed
514
			return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : [];
w  
Qiang Xue committed
515
		}
w  
Qiang Xue committed
516 517
	}

Qiang Xue committed
518 519
	/**
	 * Returns the first error of every attribute in the model.
Qiang Xue committed
520 521
	 * @return array the first errors. The array keys are the attribute names, and the array
	 * values are the corresponding error messages. An empty array will be returned if there is no error.
522 523
	 * @see getErrors()
	 * @see getFirstError()
Qiang Xue committed
524 525 526 527
	 */
	public function getFirstErrors()
	{
		if (empty($this->_errors)) {
Alexander Makarov committed
528
			return [];
Qiang Xue committed
529
		} else {
Alexander Makarov committed
530
			$errors = [];
Qiang Xue committed
531 532 533
			foreach ($this->_errors as $name => $es) {
				if (!empty($es)) {
					$errors[$name] = reset($es);
Qiang Xue committed
534 535
				}
			}
Qiang Xue committed
536
			return $errors;
Qiang Xue committed
537 538 539
		}
	}

w  
Qiang Xue committed
540 541 542 543
	/**
	 * Returns the first error of the specified attribute.
	 * @param string $attribute attribute name.
	 * @return string the error message. Null is returned if no error.
544 545
	 * @see getErrors()
	 * @see getFirstErrors()
w  
Qiang Xue committed
546
	 */
Qiang Xue committed
547
	public function getFirstError($attribute)
w  
Qiang Xue committed
548 549 550 551 552 553 554 555 556
	{
		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
	 */
557
	public function addError($attribute, $error = '')
w  
Qiang Xue committed
558
	{
w  
Qiang Xue committed
559
		$this->_errors[$attribute][] = $error;
w  
Qiang Xue committed
560 561 562 563 564 565
	}

	/**
	 * 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
566
	public function clearErrors($attribute = null)
w  
Qiang Xue committed
567
	{
w  
Qiang Xue committed
568
		if ($attribute === null) {
Alexander Makarov committed
569
			$this->_errors = [];
Qiang Xue committed
570
		} else {
w  
Qiang Xue committed
571
			unset($this->_errors[$attribute]);
w  
Qiang Xue committed
572
		}
w  
Qiang Xue committed
573 574 575
	}

	/**
w  
Qiang Xue committed
576 577
	 * 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
578
	 * changing the first letter of each word to upper case.
w  
Qiang Xue committed
579
	 * For example, 'department_name' or 'DepartmentName' will generate 'Department Name'.
w  
Qiang Xue committed
580 581 582 583 584
	 * @param string $name the column name
	 * @return string the attribute label
	 */
	public function generateAttributeLabel($name)
	{
585
		return Inflector::camel2words($name, true);
w  
Qiang Xue committed
586 587 588
	}

	/**
w  
Qiang Xue committed
589
	 * Returns attribute values.
w  
Qiang Xue committed
590
	 * @param array $names list of attributes whose value needs to be returned.
591
	 * Defaults to null, meaning all attributes listed in [[attributes()]] will be returned.
w  
Qiang Xue committed
592
	 * If it is an array, only the attributes in the array will be returned.
593
	 * @param array $except list of attributes whose value should NOT be returned.
resurtm committed
594
	 * @return array attribute values (name => value).
w  
Qiang Xue committed
595
	 */
Alexander Makarov committed
596
	public function getAttributes($names = null, $except = [])
w  
Qiang Xue committed
597
	{
Alexander Makarov committed
598
		$values = [];
599 600 601 602 603 604 605 606
		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
607 608 609
		}

		return $values;
w  
Qiang Xue committed
610 611 612 613
	}

	/**
	 * Sets the attribute values in a massive way.
resurtm committed
614
	 * @param array $values attribute values (name => value) to be assigned to the model.
w  
Qiang Xue committed
615
	 * @param boolean $safeOnly whether the assignments should only be done to the safe attributes.
w  
Qiang Xue committed
616
	 * A safe attribute is one that is associated with a validation rule in the current [[scenario]].
617 618
	 * @see safeAttributes()
	 * @see attributes()
w  
Qiang Xue committed
619
	 */
w  
Qiang Xue committed
620
	public function setAttributes($values, $safeOnly = true)
w  
Qiang Xue committed
621
	{
w  
Qiang Xue committed
622
		if (is_array($values)) {
623
			$attributes = array_flip($safeOnly ? $this->safeAttributes() : $this->attributes());
w  
Qiang Xue committed
624 625 626
			foreach ($values as $name => $value) {
				if (isset($attributes[$name])) {
					$this->$name = $value;
Qiang Xue committed
627
				} elseif ($safeOnly) {
w  
Qiang Xue committed
628 629 630
					$this->onUnsafeAttribute($name, $value);
				}
			}
w  
Qiang Xue committed
631 632 633 634 635 636 637 638 639 640
		}
	}

	/**
	 * 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
641
	public function onUnsafeAttribute($name, $value)
w  
Qiang Xue committed
642
	{
w  
Qiang Xue committed
643
		if (YII_DEBUG) {
Qiang Xue committed
644
			Yii::trace("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __METHOD__);
w  
Qiang Xue committed
645
		}
w  
Qiang Xue committed
646 647 648 649 650 651 652 653
	}

	/**
	 * Returns the scenario that this model is used in.
	 *
	 * Scenario affects how validation is performed and which attributes can
	 * be massively assigned.
	 *
654
	 * @return string the scenario that this model is in. Defaults to [[SCENARIO_DEFAULT]].
w  
Qiang Xue committed
655 656 657 658 659 660 661 662
	 */
	public function getScenario()
	{
		return $this->_scenario;
	}

	/**
	 * Sets the scenario for the model.
663 664
	 * Note that this method does not check if the scenario exists or not.
	 * The method [[validate()]] will perform this check.
w  
Qiang Xue committed
665 666 667 668
	 * @param string $value the scenario that this model is in.
	 */
	public function setScenario($value)
	{
w  
Qiang Xue committed
669
		$this->_scenario = $value;
w  
Qiang Xue committed
670 671 672
	}

	/**
673
	 * Returns the attribute names that are safe to be massively assigned in the current scenario.
674
	 * @return string[] safe attribute names
w  
Qiang Xue committed
675
	 */
676
	public function safeAttributes()
w  
Qiang Xue committed
677
	{
678
		$scenario = $this->getScenario();
679
		$scenarios = $this->scenarios();
680
		if (!isset($scenarios[$scenario])) {
Alexander Makarov committed
681
			return [];
682
		}
Alexander Makarov committed
683
		$attributes = [];
684 685 686
		foreach ($scenarios[$scenario] as $attribute) {
			if ($attribute[0] !== '!') {
				$attributes[] = $attribute;
w  
Qiang Xue committed
687 688
			}
		}
Qiang Xue committed
689
		return $attributes;
690
	}
w  
Qiang Xue committed
691

692 693
	/**
	 * Returns the attribute names that are subject to validation in the current scenario.
694
	 * @return string[] safe attribute names
695 696 697
	 */
	public function activeAttributes()
	{
698
		$scenario = $this->getScenario();
699
		$scenarios = $this->scenarios();
700
		if (!isset($scenarios[$scenario])) {
Alexander Makarov committed
701
			return [];
w  
Qiang Xue committed
702
		}
703
		$attributes = $scenarios[$scenario];
704 705 706 707 708 709
		foreach ($attributes as $i => $attribute) {
			if ($attribute[0] === '!') {
				$attributes[$i] = substr($attribute, 1);
			}
		}
		return $attributes;
w  
Qiang Xue committed
710 711
	}

712 713
	/**
	 * Populates the model with the data from end user.
714 715 716
	 * 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()]].
717 718
	 * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array
	 * supplied by end user.
719 720
	 * @param string $formName the form name to be used for loading the data into the model.
	 * If not set, [[formName()]] will be used.
721 722
	 * @return boolean whether the model is successfully populated with some data.
	 */
723
	public function load($data, $formName = null)
724
	{
725
		$scope = $formName === null ? $this->formName() : $formName;
726
		if ($scope == '' && !empty($data)) {
727 728 729 730 731 732 733 734 735 736
			$this->setAttributes($data);
			return true;
		} elseif (isset($data[$scope])) {
			$this->setAttributes($data[$scope]);
			return true;
		} else {
			return false;
		}
	}

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
	/**
	 * 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])) {
765
				$model->setAttributes($data[$scope][$i]);
766 767 768 769 770 771 772 773
				$success = true;
			}
		}
		return $success;
	}

	/**
	 * Validates multiple models.
774 775
	 * This method will validate every model. The models being validated may
	 * be of the same or different types.
776
	 * @param array $models the models to be validated
777 778 779
	 * @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.
780 781 782
	 * @return boolean whether all models are valid. False will be returned if one
	 * or multiple models have validation error.
	 */
783
	public static function validateMultiple($models, $attributes = null)
784 785 786 787
	{
		$valid = true;
		/** @var Model $model */
		foreach ($models as $model) {
788
			$valid = $model->validate($attributes) && $valid;
789 790 791 792
		}
		return $valid;
	}

Qiang Xue committed
793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829
	/**
	 * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified.
	 *
	 * A field is a named element in the returned array by [[toArray()]].
	 *
	 * This method should return an array of field names or field definitions.
	 * If the former, the field name will be treated as an object property name whose value will be used
	 * as the field value. If the latter, the array key should be the field name while the array value should be
	 * the corresponding field definition which can be either an object property name or a PHP callable
	 * returning the corresponding field value. The signature of the callable should be:
	 *
	 * ```php
	 * function ($field, $model) {
	 *     // return field value
	 * }
	 * ```
	 *
	 * For example, the following code declares four fields:
	 *
	 * - `email`: the field name is the same as the property name `email`;
	 * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their
	 *   values are obtained from the `first_name` and `last_name` properties;
	 * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name`
	 *   and `last_name`.
	 *
	 * ```php
	 * return [
	 *     'email',
	 *     'firstName' => 'first_name',
	 *     'lastName' => 'last_name',
	 *     'fullName' => function () {
	 *         return $this->first_name . ' ' . $this->last_name;
	 *     },
	 * ];
	 * ```
	 *
	 * In this method, you may also want to return different lists of fields based on some context
Qiang Xue committed
830 831
	 * information. For example, depending on [[scenario]] or the privilege of the current application user,
	 * you may return different sets of visible fields or filter out some fields.
Qiang Xue committed
832 833 834 835 836 837 838 839 840 841 842 843 844 845
	 *
	 * The default implementation of this method returns [[attributes()]] indexed by the same attribute names.
	 *
	 * @return array the list of field names or field definitions.
	 * @see toArray()
	 */
	public function fields()
	{
		$fields = $this->attributes();
		return array_combine($fields, $fields);
	}

	/**
	 * Determines which fields can be returned by [[toArray()]].
Qiang Xue committed
846
	 * This method will check the requested fields against those declared in [[fields()]] and [[extraFields()]]
Qiang Xue committed
847 848 849 850 851 852 853
	 * to determine which fields can be returned.
	 * @param array $fields the fields being requested for exporting
	 * @param array $expand the additional fields being requested for exporting
	 * @return array the list of fields to be exported. The array keys are the field names, and the array values
	 * are the corresponding object property names or PHP callables returning the field values.
	 */
	protected function resolveFields(array $fields, array $expand)
Qiang Xue committed
854
	{
Qiang Xue committed
855 856 857 858 859 860 861 862 863 864 865 866 867 868 869
		$result = [];

		foreach ($this->fields() as $field => $definition) {
			if (is_integer($field)) {
				$field = $definition;
			}
			if (empty($fields) || in_array($field, $fields, true)) {
				$result[$field] = $definition;
			}
		}

		if (empty($expand)) {
			return $result;
		}

Qiang Xue committed
870
		foreach ($this->extraFields() as $field => $definition) {
Qiang Xue committed
871 872 873 874 875 876 877 878 879
			if (is_integer($field)) {
				$field = $definition;
			}
			if (in_array($field, $expand, true)) {
				$result[$field] = $definition;
			}
		}

		return $result;
Qiang Xue committed
880 881
	}

w  
Qiang Xue committed
882 883 884
	/**
	 * Returns an iterator for traversing the attributes in the model.
	 * This method is required by the interface IteratorAggregate.
885
	 * @return ArrayIterator an iterator for traversing the items in the list.
w  
Qiang Xue committed
886 887 888
	 */
	public function getIterator()
	{
w  
Qiang Xue committed
889
		$attributes = $this->getAttributes();
890
		return new ArrayIterator($attributes);
w  
Qiang Xue committed
891 892 893 894
	}

	/**
	 * Returns whether there is an element at the specified offset.
w  
Qiang Xue committed
895 896
	 * 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
897 898 899 900 901
	 * @param mixed $offset the offset to check on
	 * @return boolean
	 */
	public function offsetExists($offset)
	{
Qiang Xue committed
902
		return $this->$offset !== null;
w  
Qiang Xue committed
903 904 905 906
	}

	/**
	 * Returns the element at the specified offset.
w  
Qiang Xue committed
907 908
	 * 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
909
	 * @param mixed $offset the offset to retrieve element.
w  
Qiang Xue committed
910 911 912 913 914 915 916 917 918
	 * @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
919 920
	 * 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
921 922 923
	 * @param integer $offset the offset to set element
	 * @param mixed $item the element value
	 */
w  
Qiang Xue committed
924
	public function offsetSet($offset, $item)
w  
Qiang Xue committed
925
	{
w  
Qiang Xue committed
926
		$this->$offset = $item;
w  
Qiang Xue committed
927 928 929
	}

	/**
930
	 * Sets the element value at the specified offset to null.
w  
Qiang Xue committed
931 932
	 * 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
933 934 935 936
	 * @param mixed $offset the offset to unset element
	 */
	public function offsetUnset($offset)
	{
937
		$this->$offset = null;
w  
Qiang Xue committed
938 939
	}
}