Completed
Pull Request — master (#11845)
by Thiago
08:40
created

Model::addErrors()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.2
c 0
b 0
f 0
ccs 10
cts 10
cp 1
cc 4
eloc 7
nc 3
nop 1
crap 4
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\base;
9
10
use Yii;
11
use ArrayAccess;
12
use ArrayObject;
13
use ArrayIterator;
14
use ReflectionClass;
15
use IteratorAggregate;
16
use yii\helpers\Inflector;
17
use yii\validators\RequiredValidator;
18
use yii\validators\Validator;
19
20
/**
21
 * Model is the base class for data models.
22
 *
23
 * Model implements the following commonly used features:
24
 *
25
 * - attribute declaration: by default, every public class member is considered as
26
 *   a model attribute
27
 * - attribute labels: each attribute may be associated with a label for display purpose
28
 * - massive attribute assignment
29
 * - scenario-based validation
30
 *
31
 * Model also raises the following events when performing data validation:
32
 *
33
 * - [[EVENT_BEFORE_VALIDATE]]: an event raised at the beginning of [[validate()]]
34
 * - [[EVENT_AFTER_VALIDATE]]: an event raised at the end of [[validate()]]
35
 *
36
 * You may directly use Model to store model data, or extend it with customization.
37
 *
38
 * @property \yii\validators\Validator[] $activeValidators The validators applicable to the current
39
 * [[scenario]]. This property is read-only.
40
 * @property array $attributes Attribute values (name => value).
41
 * @property array $errors An array of errors for all attributes. Empty array is returned if no error. The
42
 * result is a two-dimensional array. See [[getErrors()]] for detailed description. This property is read-only.
43
 * @property array $firstErrors The first errors. The array keys are the attribute names, and the array values
44
 * are the corresponding error messages. An empty array will be returned if there is no error. This property is
45
 * read-only.
46
 * @property ArrayIterator $iterator An iterator for traversing the items in the list. This property is
47
 * read-only.
48
 * @property string $scenario The scenario that this model is in. Defaults to [[SCENARIO_DEFAULT]].
49
 * @property ArrayObject|\yii\validators\Validator[] $validators All the validators declared in the model.
50
 * This property is read-only.
51
 *
52
 * @author Qiang Xue <[email protected]>
53
 * @since 2.0
54
 */
55
class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayable
56
{
57
    use ArrayableTrait;
58
59
    /**
60
     * The name of the default scenario.
61
     */
62
    const SCENARIO_DEFAULT = 'default';
63
    /**
64
     * @event ModelEvent an event raised at the beginning of [[validate()]]. You may set
65
     * [[ModelEvent::isValid]] to be false to stop the validation.
66
     */
67
    const EVENT_BEFORE_VALIDATE = 'beforeValidate';
68
    /**
69
     * @event Event an event raised at the end of [[validate()]]
70
     */
71
    const EVENT_AFTER_VALIDATE = 'afterValidate';
72
73
    /**
74
     * @var array validation errors (attribute name => array of errors)
75
     */
76
    private $_errors;
77
    /**
78
     * @var ArrayObject list of validators
79
     */
80
    private $_validators;
81
    /**
82
     * @var string current scenario
83
     */
84
    private $_scenario = self::SCENARIO_DEFAULT;
85
86
87
    /**
88
     * Returns the validation rules for attributes.
89
     *
90
     * Validation rules are used by [[validate()]] to check if attribute values are valid.
91
     * Child classes may override this method to declare different validation rules.
92
     *
93
     * Each rule is an array with the following structure:
94
     *
95
     * ```php
96
     * [
97
     *     ['attribute1', 'attribute2'],
98
     *     'validator type',
99
     *     'on' => ['scenario1', 'scenario2'],
100
     *     //...other parameters...
101
     * ]
102
     * ```
103
     *
104
     * where
105
     *
106
     *  - attribute list: required, specifies the attributes array to be validated, for single attribute you can pass a string;
107
     *  - validator type: required, specifies the validator to be used. It can be a built-in validator name,
108
     *    a method name of the model class, an anonymous function, or a validator class name.
109
     *  - on: optional, specifies the [[scenario|scenarios]] array in which the validation
110
     *    rule can be applied. If this option is not set, the rule will apply to all scenarios.
111
     *  - additional name-value pairs can be specified to initialize the corresponding validator properties.
112
     *    Please refer to individual validator class API for possible properties.
113
     *
114
     * A validator can be either an object of a class extending [[Validator]], or a model class method
115
     * (called *inline validator*) that has the following signature:
116
     *
117
     * ```php
118
     * // $params refers to validation parameters given in the rule
119
     * function validatorName($attribute, $params)
120
     * ```
121
     *
122
     * In the above `$attribute` refers to the attribute currently being validated while `$params` contains an array of
123
     * validator configuration options such as `max` in case of `string` validator. The value of the attribute currently being validated
124
     * can be accessed as `$this->$attribute`. Note the `$` before `attribute`; this is taking the value of the variable
125
     * `$attribute` and using it as the name of the property to access.
126
     *
127
     * Yii also provides a set of [[Validator::builtInValidators|built-in validators]].
128
     * Each one has an alias name which can be used when specifying a validation rule.
129
     *
130
     * Below are some examples:
131
     *
132
     * ```php
133
     * [
134
     *     // built-in "required" validator
135
     *     [['username', 'password'], 'required'],
136
     *     // built-in "string" validator customized with "min" and "max" properties
137
     *     ['username', 'string', 'min' => 3, 'max' => 12],
138
     *     // built-in "compare" validator that is used in "register" scenario only
139
     *     ['password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'],
140
     *     // an inline validator defined via the "authenticate()" method in the model class
141
     *     ['password', 'authenticate', 'on' => 'login'],
142
     *     // a validator of class "DateRangeValidator"
143
     *     ['dateRange', 'DateRangeValidator'],
144
     * ];
145
     * ```
146
     *
147
     * Note, in order to inherit rules defined in the parent class, a child class needs to
148
     * merge the parent rules with child rules using functions such as `array_merge()`.
149
     *
150
     * @return array validation rules
151
     * @see scenarios()
152
     */
153 33
    public function rules()
154
    {
155 33
        return [];
156
    }
157
158
    /**
159
     * Returns a list of scenarios and the corresponding active attributes.
160
     * An active attribute is one that is subject to validation in the current scenario.
161
     * The returned array should be in the following format:
162
     *
163
     * ```php
164
     * [
165
     *     'scenario1' => ['attribute11', 'attribute12', ...],
166
     *     'scenario2' => ['attribute21', 'attribute22', ...],
167
     *     ...
168
     * ]
169
     * ```
170
     *
171
     * By default, an active attribute is considered safe and can be massively assigned.
172
     * If an attribute should NOT be massively assigned (thus considered unsafe),
173
     * please prefix the attribute with an exclamation character (e.g. `'!rank'`).
174
     *
175
     * The default implementation of this method will return all scenarios found in the [[rules()]]
176
     * declaration. A special scenario named [[SCENARIO_DEFAULT]] will contain all attributes
177
     * found in the [[rules()]]. Each scenario will be associated with the attributes that
178
     * are being validated by the validation rules that apply to the scenario.
179
     *
180
     * @return array a list of scenarios and the corresponding active attributes.
181
     */
182 26
    public function scenarios()
183
    {
184 26
        $scenarios = [self::SCENARIO_DEFAULT => []];
185 26
        foreach ($this->getValidators() as $validator) {
186 10
            foreach ($validator->on as $scenario) {
187 4
                $scenarios[$scenario] = [];
188 10
            }
189 10
            foreach ($validator->except as $scenario) {
190 1
                $scenarios[$scenario] = [];
191 10
            }
192 26
        }
193 26
        $names = array_keys($scenarios);
194
195 26
        foreach ($this->getValidators() as $validator) {
196 10
            if (empty($validator->on) && empty($validator->except)) {
197 10
                foreach ($names as $name) {
198 10
                    foreach ($validator->attributes as $attribute) {
199 10
                        $scenarios[$name][$attribute] = true;
200 10
                    }
201 10
                }
202 10
            } elseif (empty($validator->on)) {
203 1
                foreach ($names as $name) {
204 1
                    if (!in_array($name, $validator->except, true)) {
205 1
                        foreach ($validator->attributes as $attribute) {
206 1
                            $scenarios[$name][$attribute] = true;
207 1
                        }
208 1
                    }
209 1
                }
210 1
            } else {
211 4
                foreach ($validator->on as $name) {
212 4
                    foreach ($validator->attributes as $attribute) {
213 4
                        $scenarios[$name][$attribute] = true;
214 4
                    }
215 4
                }
216
            }
217 26
        }
218
219 26
        foreach ($scenarios as $scenario => $attributes) {
220 26
            if (!empty($attributes)) {
221 10
                $scenarios[$scenario] = array_keys($attributes);
222 10
            }
223 26
        }
224
225 26
        return $scenarios;
226
    }
227
228
    /**
229
     * Returns the form name that this model class should use.
230
     *
231
     * The form name is mainly used by [[\yii\widgets\ActiveForm]] to determine how to name
232
     * the input fields for the attributes in a model. If the form name is "A" and an attribute
233
     * name is "b", then the corresponding input name would be "A[b]". If the form name is
234
     * an empty string, then the input name would be "b".
235
     *
236
     * The purpose of the above naming schema is that for forms which contain multiple different models,
237
     * the attributes of each model are grouped in sub-arrays of the POST-data and it is easier to
238
     * differentiate between them.
239
     *
240
     * By default, this method returns the model class name (without the namespace part)
241
     * as the form name. You may override it when the model is used in different forms.
242
     *
243
     * @return string the form name of this model class.
244
     * @see load()
245
     */
246 33
    public function formName()
247
    {
248 30
        $reflector = new ReflectionClass($this);
249 33
        return $reflector->getShortName();
250
    }
251
252
    /**
253
     * Returns the list of attribute names.
254
     * By default, this method returns all public non-static properties of the class.
255
     * You may override this method to change the default behavior.
256
     * @return array list of attribute names.
257
     */
258 3
    public function attributes()
259
    {
260 3
        $class = new ReflectionClass($this);
261 3
        $names = [];
262 3
        foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
263 3
            if (!$property->isStatic()) {
264 3
                $names[] = $property->getName();
265 3
            }
266 3
        }
267
268 3
        return $names;
269
    }
270
271
    /**
272
     * Returns the attribute labels.
273
     *
274
     * Attribute labels are mainly used for display purpose. For example, given an attribute
275
     * `firstName`, we can declare a label `First Name` which is more user-friendly and can
276
     * be displayed to end users.
277
     *
278
     * By default an attribute label is generated using [[generateAttributeLabel()]].
279
     * This method allows you to explicitly specify attribute labels.
280
     *
281
     * Note, in order to inherit labels defined in the parent class, a child class needs to
282
     * merge the parent labels with child labels using functions such as `array_merge()`.
283
     *
284
     * @return array attribute labels (name => label)
285
     * @see generateAttributeLabel()
286
     */
287 42
    public function attributeLabels()
288
    {
289 42
        return [];
290
    }
291
292
    /**
293
     * Returns the attribute hints.
294
     *
295
     * Attribute hints are mainly used for display purpose. For example, given an attribute
296
     * `isPublic`, we can declare a hint `Whether the post should be visible for not logged in users`,
297
     * which provides user-friendly description of the attribute meaning and can be displayed to end users.
298
     *
299
     * Unlike label hint will not be generated, if its explicit declaration is omitted.
300
     *
301
     * Note, in order to inherit hints defined in the parent class, a child class needs to
302
     * merge the parent hints with child hints using functions such as `array_merge()`.
303
     *
304
     * @return array attribute hints (name => hint)
305
     * @since 2.0.4
306
     */
307 6
    public function attributeHints()
308
    {
309 6
        return [];
310
    }
311
312
    /**
313
     * Performs the data validation.
314
     *
315
     * This method executes the validation rules applicable to the current [[scenario]].
316
     * The following criteria are used to determine whether a rule is currently applicable:
317
     *
318
     * - the rule must be associated with the attributes relevant to the current scenario;
319
     * - the rules must be effective for the current scenario.
320
     *
321
     * This method will call [[beforeValidate()]] and [[afterValidate()]] before and
322
     * after the actual validation, respectively. If [[beforeValidate()]] returns false,
323
     * the validation will be cancelled and [[afterValidate()]] will not be called.
324
     *
325
     * Errors found during the validation can be retrieved via [[getErrors()]],
326
     * [[getFirstErrors()]] and [[getFirstError()]].
327
     *
328
     * @param array $attributeNames list of attribute names that should be validated.
329
     * If this parameter is empty, it means any attribute listed in the applicable
330
     * validation rules should be validated.
331
     * @param boolean $clearErrors whether to call [[clearErrors()]] before performing validation
332
     * @return boolean whether the validation is successful without any error.
333
     * @throws InvalidParamException if the current scenario is unknown.
334
     */
335 19
    public function validate($attributeNames = null, $clearErrors = true)
336
    {
337 19
        if ($clearErrors) {
338 19
            $this->clearErrors();
339 19
        }
340
341 19
        if (!$this->beforeValidate()) {
342
            return false;
343
        }
344
345 19
        $scenarios = $this->scenarios();
346 19
        $scenario = $this->getScenario();
347 19
        if (!isset($scenarios[$scenario])) {
348
            throw new InvalidParamException("Unknown scenario: $scenario");
349
        }
350
351 19
        if ($attributeNames === null) {
352 19
            $attributeNames = $this->activeAttributes();
353 19
        }
354
355 19
        foreach ($this->getActiveValidators() as $validator) {
356 4
            $validator->validateAttributes($this, $attributeNames);
357 19
        }
358 19
        $this->afterValidate();
359
360 19
        return !$this->hasErrors();
361
    }
362
363
    /**
364
     * This method is invoked before validation starts.
365
     * The default implementation raises a `beforeValidate` event.
366
     * You may override this method to do preliminary checks before validation.
367
     * Make sure the parent implementation is invoked so that the event can be raised.
368
     * @return boolean whether the validation should be executed. Defaults to true.
369
     * If false is returned, the validation will stop and the model is considered invalid.
370
     */
371 19
    public function beforeValidate()
372
    {
373 19
        $event = new ModelEvent;
374 19
        $this->trigger(self::EVENT_BEFORE_VALIDATE, $event);
375
376 19
        return $event->isValid;
377
    }
378
379
    /**
380
     * This method is invoked after validation ends.
381
     * The default implementation raises an `afterValidate` event.
382
     * You may override this method to do postprocessing after validation.
383
     * Make sure the parent implementation is invoked so that the event can be raised.
384
     */
385 19
    public function afterValidate()
386
    {
387 19
        $this->trigger(self::EVENT_AFTER_VALIDATE);
388 19
    }
389
390
    /**
391
     * Returns all the validators declared in [[rules()]].
392
     *
393
     * This method differs from [[getActiveValidators()]] in that the latter
394
     * only returns the validators applicable to the current [[scenario]].
395
     *
396
     * Because this method returns an ArrayObject object, you may
397
     * manipulate it by inserting or removing validators (useful in model behaviors).
398
     * For example,
399
     *
400
     * ```php
401
     * $model->validators[] = $newValidator;
402
     * ```
403
     *
404
     * @return ArrayObject|\yii\validators\Validator[] all the validators declared in the model.
405
     */
406 40
    public function getValidators()
407
    {
408 40
        if ($this->_validators === null) {
409 40
            $this->_validators = $this->createValidators();
410 40
        }
411 40
        return $this->_validators;
412
    }
413
414
    /**
415
     * Returns the validators applicable to the current [[scenario]].
416
     * @param string $attribute the name of the attribute whose applicable validators should be returned.
417
     * If this is null, the validators for ALL attributes in the model will be returned.
418
     * @return \yii\validators\Validator[] the validators applicable to the current [[scenario]].
419
     */
420 35
    public function getActiveValidators($attribute = null)
421
    {
422 35
        $validators = [];
423 35
        $scenario = $this->getScenario();
424 35
        foreach ($this->getValidators() as $validator) {
425 13
            if ($validator->isActive($scenario) && ($attribute === null || in_array($attribute, $validator->attributes, true))) {
426 13
                $validators[] = $validator;
427 13
            }
428 35
        }
429 35
        return $validators;
430
    }
431
432
    /**
433
     * Creates validator objects based on the validation rules specified in [[rules()]].
434
     * Unlike [[getValidators()]], each time this method is called, a new list of validators will be returned.
435
     * @return ArrayObject validators
436
     * @throws InvalidConfigException if any validation rule configuration is invalid
437
     */
438 41
    public function createValidators()
439
    {
440 41
        $validators = new ArrayObject;
441 41
        foreach ($this->rules() as $rule) {
442 9
            if ($rule instanceof Validator) {
443
                $validators->append($rule);
444 9
            } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
445 8
                $validator = Validator::createValidator($rule[1], $this, (array) $rule[0], array_slice($rule, 2));
446 8
                $validators->append($validator);
447 8
            } else {
448 1
                throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
449
            }
450 40
        }
451 40
        return $validators;
452
    }
453
454
    /**
455
     * Returns a value indicating whether the attribute is required.
456
     * This is determined by checking if the attribute is associated with a
457
     * [[\yii\validators\RequiredValidator|required]] validation rule in the
458
     * current [[scenario]].
459
     *
460
     * Note that when the validator has a conditional validation applied using
461
     * [[\yii\validators\RequiredValidator::$when|$when]] this method will return
462
     * `false` regardless of the `when` condition because it may be called be
463
     * before the model is loaded with data.
464
     *
465
     * @param string $attribute attribute name
466
     * @return boolean whether the attribute is required
467
     */
468 10
    public function isAttributeRequired($attribute)
469
    {
470 10
        foreach ($this->getActiveValidators($attribute) as $validator) {
471 3
            if ($validator instanceof RequiredValidator && $validator->when === null) {
472 3
                return true;
473
            }
474 8
        }
475 8
        return false;
476
    }
477
478
    /**
479
     * Returns a value indicating whether the attribute is safe for massive assignments.
480
     * @param string $attribute attribute name
481
     * @return boolean whether the attribute is safe for massive assignments
482
     * @see safeAttributes()
483
     */
484 1
    public function isAttributeSafe($attribute)
485
    {
486 1
        return in_array($attribute, $this->safeAttributes(), true);
487
    }
488
489
    /**
490
     * Returns a value indicating whether the attribute is active in the current scenario.
491
     * @param string $attribute attribute name
492
     * @return boolean whether the attribute is active in the current scenario
493
     * @see activeAttributes()
494
     */
495
    public function isAttributeActive($attribute)
496
    {
497
        return in_array($attribute, $this->activeAttributes(), true);
498
    }
499
500
    /**
501
     * Returns the text label for the specified attribute.
502
     * @param string $attribute the attribute name
503
     * @return string the attribute label
504
     * @see generateAttributeLabel()
505
     * @see attributeLabels()
506
     */
507 12
    public function getAttributeLabel($attribute)
508
    {
509 12
        $labels = $this->attributeLabels();
510 12
        return isset($labels[$attribute]) ? $labels[$attribute] : $this->generateAttributeLabel($attribute);
511
    }
512
513
    /**
514
     * Returns the text hint for the specified attribute.
515
     * @param string $attribute the attribute name
516
     * @return string the attribute hint
517
     * @see attributeHints()
518
     * @since 2.0.4
519
     */
520 6
    public function getAttributeHint($attribute)
521
    {
522 6
        $hints = $this->attributeHints();
523 6
        return isset($hints[$attribute]) ? $hints[$attribute] : '';
524
    }
525
526
    /**
527
     * Returns a value indicating whether there is any validation error.
528
     * @param string|null $attribute attribute name. Use null to check all attributes.
529
     * @return boolean whether there is any error.
530
     */
531 219
    public function hasErrors($attribute = null)
532
    {
533 219
        return $attribute === null ? !empty($this->_errors) : isset($this->_errors[$attribute]);
534
    }
535
536
    /**
537
     * Returns the errors for all attribute or a single attribute.
538
     * @param string $attribute attribute name. Use null to retrieve errors for all attributes.
539
     * @property array An array of errors for all attributes. Empty array is returned if no error.
540
     * The result is a two-dimensional array. See [[getErrors()]] for detailed description.
541
     * @return array errors for all attributes or the specified attribute. Empty array is returned if no error.
542
     * Note that when returning errors for all attributes, the result is a two-dimensional array, like the following:
543
     *
544
     * ```php
545
     * [
546
     *     'username' => [
547
     *         'Username is required.',
548
     *         'Username must contain only word characters.',
549
     *     ],
550
     *     'email' => [
551
     *         'Email address is invalid.',
552
     *     ]
553
     * ]
554
     * ```
555
     *
556
     * @see getFirstErrors()
557
     * @see getFirstError()
558
     */
559 20
    public function getErrors($attribute = null)
560
    {
561 20
        if ($attribute === null) {
562 2
            return $this->_errors === null ? [] : $this->_errors;
563
        } else {
564 19
            return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : [];
565
        }
566
    }
567
568
    /**
569
     * Returns the first error of every attribute in the model.
570
     * @return array the first errors. The array keys are the attribute names, and the array
571
     * values are the corresponding error messages. An empty array will be returned if there is no error.
572
     * @see getErrors()
573
     * @see getFirstError()
574
     */
575 1
    public function getFirstErrors()
576
    {
577 1
        if (empty($this->_errors)) {
578 1
            return [];
579
        } else {
580 1
            $errors = [];
581 1
            foreach ($this->_errors as $name => $es) {
582 1
                if (!empty($es)) {
583 1
                    while (is_array($es)) {
584 1
                        $es = reset($es);
585 1
                    }
586 1
                    $errors[$name] = $es;
587 1
                }
588 1
            }
589
590 1
            return $errors;
591
        }
592
    }
593
594
    /**
595
     * Returns the first error of the specified attribute.
596
     * @param string $attribute attribute name.
597
     * @return string the error message. Null is returned if no error.
598
     * @see getErrors()
599
     * @see getFirstErrors()
600
     */
601 14
    public function getFirstError($attribute)
602
    {
603 14
        return isset($this->_errors[$attribute]) ? reset($this->_errors[$attribute]) : null;
604
    }
605
606
    /**
607
     * Adds a new error to the specified attribute.
608
     * @param string $attribute attribute name
609
     * @param string $error new error message
610
     */
611 76
    public function addError($attribute, $error = '')
612
    {
613 76
        $this->_errors[$attribute][] = $error;
614 76
    }
615
616
    /**
617
     * Adds a list of errors.
618
     * @param array $items a list of errors. The array keys must be attribute names.
619
     * The array values should be error messages. If an attribute has multiple errors,
620
     * these errors must be given in terms of an array.
621
     * You may use the result of [[getErrors()]] as the value for this parameter.
622
     * @since 2.0.2
623
     */
624 5
    public function addErrors(array $items)
625
    {
626 5
        foreach ($items as $attribute => $errors) {
627 5
            if (is_array($errors)) {
628 5
                foreach ($errors as $error) {
629 5
                    $this->addError($attribute, $error);
630 5
                }
631 5
            } else {
632 1
                $this->addError($attribute, $errors);
633
            }
634 5
        }
635 5
    }
636
637
    /**
638
     * Removes errors for all attributes or a single attribute.
639
     * @param string $attribute attribute name. Use null to remove errors for all attribute.
640
     */
641 40
    public function clearErrors($attribute = null)
642
    {
643 40
        if ($attribute === null) {
644 35
            $this->_errors = [];
645 35
        } else {
646 9
            unset($this->_errors[$attribute]);
647
        }
648 40
    }
649
650
    /**
651
     * Generates a user friendly attribute label based on the give attribute name.
652
     * This is done by replacing underscores, dashes and dots with blanks and
653
     * changing the first letter of each word to upper case.
654
     * For example, 'department_name' or 'DepartmentName' will generate 'Department Name'.
655
     * @param string $name the column name
656
     * @return string the attribute label
657
     */
658 49
    public function generateAttributeLabel($name)
659
    {
660 49
        return Inflector::camel2words($name, true);
661
    }
662
663
    /**
664
     * Returns attribute values.
665
     * @param array $names list of attributes whose value needs to be returned.
666
     * Defaults to null, meaning all attributes listed in [[attributes()]] will be returned.
667
     * If it is an array, only the attributes in the array will be returned.
668
     * @param array $except list of attributes whose value should NOT be returned.
669
     * @return array attribute values (name => value).
670
     */
671 2
    public function getAttributes($names = null, $except = [])
672
    {
673 2
        $values = [];
674 2
        if ($names === null) {
675 2
            $names = $this->attributes();
676 2
        }
677 2
        foreach ($names as $name) {
678 2
            $values[$name] = $this->$name;
679 2
        }
680 2
        foreach ($except as $name) {
681 1
            unset($values[$name]);
682 2
        }
683
684 2
        return $values;
685
    }
686
687
    /**
688
     * Sets the attribute values in a massive way.
689
     * @param array $values attribute values (name => value) to be assigned to the model.
690
     * @param boolean $safeOnly whether the assignments should only be done to the safe attributes.
691
     * A safe attribute is one that is associated with a validation rule in the current [[scenario]].
692
     * @see safeAttributes()
693
     * @see attributes()
694
     */
695 10
    public function setAttributes($values, $safeOnly = true)
696
    {
697 10
        if (is_array($values)) {
698 10
            $attributes = array_flip($safeOnly ? $this->safeAttributes() : $this->attributes());
699 10
            foreach ($values as $name => $value) {
700 10
                if (isset($attributes[$name])) {
701 10
                    $this->$name = $value;
702 10
                } elseif ($safeOnly) {
703 2
                    $this->onUnsafeAttribute($name, $value);
704 2
                }
705 10
            }
706 10
        }
707 10
    }
708
709
    /**
710
     * This method is invoked when an unsafe attribute is being massively assigned.
711
     * The default implementation will log a warning message if YII_DEBUG is on.
712
     * It does nothing otherwise.
713
     * @param string $name the unsafe attribute name
714
     * @param mixed $value the attribute value
715
     */
716 2
    public function onUnsafeAttribute($name, $value)
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
717
    {
718 2
        if (YII_DEBUG) {
719 2
            Yii::trace("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __METHOD__);
720 2
        }
721 2
    }
722
723
    /**
724
     * Returns the scenario that this model is used in.
725
     *
726
     * Scenario affects how validation is performed and which attributes can
727
     * be massively assigned.
728
     *
729
     * @return string the scenario that this model is in. Defaults to [[SCENARIO_DEFAULT]].
730
     */
731 106
    public function getScenario()
732
    {
733 106
        return $this->_scenario;
734
    }
735
736
    /**
737
     * Sets the scenario for the model.
738
     * Note that this method does not check if the scenario exists or not.
739
     * The method [[validate()]] will perform this check.
740
     * @param string $value the scenario that this model is in.
741
     */
742 7
    public function setScenario($value)
743
    {
744 7
        $this->_scenario = $value;
745 7
    }
746
747
    /**
748
     * Returns the attribute names that are safe to be massively assigned in the current scenario.
749
     * @return string[] safe attribute names
750
     */
751 5
    public function safeAttributes()
752
    {
753 5
        $scenario = $this->getScenario();
754 5
        $scenarios = $this->scenarios();
755 5
        if (!isset($scenarios[$scenario])) {
756 3
            return [];
757
        }
758 5
        $attributes = [];
759 5
        foreach ($scenarios[$scenario] as $attribute) {
760 5
            if ($attribute[0] !== '!' && !in_array('!' . $attribute, $scenarios[$scenario])) {
761 5
                $attributes[] = $attribute;
762 5
            }
763 5
        }
764
765 5
        return $attributes;
766
    }
767
768
    /**
769
     * Returns the attribute names that are subject to validation in the current scenario.
770
     * @return string[] safe attribute names
771
     */
772 26
    public function activeAttributes()
773
    {
774 26
        $scenario = $this->getScenario();
775 26
        $scenarios = $this->scenarios();
776 26
        if (!isset($scenarios[$scenario])) {
777 2
            return [];
778
        }
779 26
        $attributes = $scenarios[$scenario];
780 26
        foreach ($attributes as $i => $attribute) {
781 10
            if ($attribute[0] === '!') {
782 2
                $attributes[$i] = substr($attribute, 1);
783 2
            }
784 26
        }
785
786 26
        return $attributes;
787
    }
788
789
    /**
790
     * Populates the model with input data.
791
     *
792
     * This method provides a convenient shortcut for:
793
     *
794
     * ```php
795
     * if (isset($_POST['FormName'])) {
796
     *     $model->attributes = $_POST['FormName'];
797
     *     if ($model->save()) {
798
     *         // handle success
799
     *     }
800
     * }
801
     * ```
802
     *
803
     * which, with `load()` can be written as:
804
     *
805
     * ```php
806
     * if ($model->load($_POST) && $model->save()) {
807
     *     // handle success
808
     * }
809
     * ```
810
     *
811
     * `load()` gets the `'FormName'` from the model's [[formName()]] method (which you may override), unless the
812
     * `$formName` parameter is given. If the form name is empty, `load()` populates the model with the whole of `$data`,
813
     * instead of `$data['FormName']`.
814
     *
815
     * Note, that the data being populated is subject to the safety check by [[setAttributes()]].
816
     *
817
     * @param array $data the data array to load, typically `$_POST` or `$_GET`.
818
     * @param string $formName the form name to use to load the data into the model.
819
     * If not set, [[formName()]] is used.
820
     * @return boolean whether `load()` found the expected form in `$data`.
821
     */
822 1
    public function load($data, $formName = null)
823
    {
824 1
        $scope = $formName === null ? $this->formName() : $formName;
825 1
        if ($scope === '' && !empty($data)) {
826 1
            $this->setAttributes($data);
827
828 1
            return true;
829 1
        } elseif (isset($data[$scope])) {
830 1
            $this->setAttributes($data[$scope]);
831
832 1
            return true;
833
        } else {
834 1
            return false;
835
        }
836
    }
837
838
    /**
839
     * Populates a set of models with the data from end user.
840
     * This method is mainly used to collect tabular data input.
841
     * The data to be loaded for each model is `$data[formName][index]`, where `formName`
842
     * refers to the value of [[formName()]], and `index` the index of the model in the `$models` array.
843
     * If [[formName()]] is empty, `$data[index]` will be used to populate each model.
844
     * The data being populated to each model is subject to the safety check by [[setAttributes()]].
845
     * @param array $models the models to be populated. Note that all models should have the same class.
846
     * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array
847
     * supplied by end user.
848
     * @param string $formName the form name to be used for loading the data into the models.
849
     * If not set, it will use the [[formName()]] value of the first model in `$models`.
850
     * This parameter is available since version 2.0.1.
851
     * @return boolean whether at least one of the models is successfully populated.
852
     */
853
    public static function loadMultiple($models, $data, $formName = null)
854
    {
855
        if ($formName === null) {
856
            /* @var $first Model */
857
            $first = reset($models);
858
            if ($first === false) {
859
                return false;
860
            }
861
            $formName = $first->formName();
862
        }
863
864
        $success = false;
865
        foreach ($models as $i => $model) {
866
            /* @var $model Model */
867
            if ($formName == '') {
868
                if (!empty($data[$i])) {
869
                    $model->load($data[$i], '');
870
                    $success = true;
871
                }
872
            } elseif (!empty($data[$formName][$i])) {
873
                $model->load($data[$formName][$i], '');
874
                $success = true;
875
            }
876
        }
877
878
        return $success;
879
    }
880
881
    /**
882
     * Validates multiple models.
883
     * This method will validate every model. The models being validated may
884
     * be of the same or different types.
885
     * @param array $models the models to be validated
886
     * @param array $attributeNames list of attribute names that should be validated.
887
     * If this parameter is empty, it means any attribute listed in the applicable
888
     * validation rules should be validated.
889
     * @return boolean whether all models are valid. False will be returned if one
890
     * or multiple models have validation error.
891
     */
892
    public static function validateMultiple($models, $attributeNames = null)
893
    {
894
        $valid = true;
895
        /* @var $model Model */
896
        foreach ($models as $model) {
897
            $valid = $model->validate($attributeNames) && $valid;
898
        }
899
900
        return $valid;
901
    }
902
903
    /**
904
     * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified.
905
     *
906
     * A field is a named element in the returned array by [[toArray()]].
907
     *
908
     * This method should return an array of field names or field definitions.
909
     * If the former, the field name will be treated as an object property name whose value will be used
910
     * as the field value. If the latter, the array key should be the field name while the array value should be
911
     * the corresponding field definition which can be either an object property name or a PHP callable
912
     * returning the corresponding field value. The signature of the callable should be:
913
     *
914
     * ```php
915
     * function ($model, $field) {
916
     *     // return field value
917
     * }
918
     * ```
919
     *
920
     * For example, the following code declares four fields:
921
     *
922
     * - `email`: the field name is the same as the property name `email`;
923
     * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their
924
     *   values are obtained from the `first_name` and `last_name` properties;
925
     * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name`
926
     *   and `last_name`.
927
     *
928
     * ```php
929
     * return [
930
     *     'email',
931
     *     'firstName' => 'first_name',
932
     *     'lastName' => 'last_name',
933
     *     'fullName' => function ($model) {
934
     *         return $model->first_name . ' ' . $model->last_name;
935
     *     },
936
     * ];
937
     * ```
938
     *
939
     * In this method, you may also want to return different lists of fields based on some context
940
     * information. For example, depending on [[scenario]] or the privilege of the current application user,
941
     * you may return different sets of visible fields or filter out some fields.
942
     *
943
     * The default implementation of this method returns [[attributes()]] indexed by the same attribute names.
944
     *
945
     * @return array the list of field names or field definitions.
946
     * @see toArray()
947
     */
948
    public function fields()
949
    {
950
        $fields = $this->attributes();
951
952
        return array_combine($fields, $fields);
953
    }
954
955
    /**
956
     * Returns an iterator for traversing the attributes in the model.
957
     * This method is required by the interface [[\IteratorAggregate]].
958
     * @return ArrayIterator an iterator for traversing the items in the list.
959
     */
960 1
    public function getIterator()
961
    {
962 1
        $attributes = $this->getAttributes();
963 1
        return new ArrayIterator($attributes);
964
    }
965
966
    /**
967
     * Returns whether there is an element at the specified offset.
968
     * This method is required by the SPL interface [[\ArrayAccess]].
969
     * It is implicitly called when you use something like `isset($model[$offset])`.
970
     * @param mixed $offset the offset to check on
971
     * @return boolean
972
     */
973 1
    public function offsetExists($offset)
974
    {
975 1
        return $this->$offset !== null;
976
    }
977
978
    /**
979
     * Returns the element at the specified offset.
980
     * This method is required by the SPL interface [[\ArrayAccess]].
981
     * It is implicitly called when you use something like `$value = $model[$offset];`.
982
     * @param mixed $offset the offset to retrieve element.
983
     * @return mixed the element at the offset, null if no element is found at the offset
984
     */
985 126
    public function offsetGet($offset)
986
    {
987 126
        return $this->$offset;
988
    }
989
990
    /**
991
     * Sets the element at the specified offset.
992
     * This method is required by the SPL interface [[\ArrayAccess]].
993
     * It is implicitly called when you use something like `$model[$offset] = $item;`.
994
     * @param integer $offset the offset to set element
995
     * @param mixed $item the element value
996
     */
997 1
    public function offsetSet($offset, $item)
998
    {
999 1
        $this->$offset = $item;
1000 1
    }
1001
1002
    /**
1003
     * Sets the element value at the specified offset to null.
1004
     * This method is required by the SPL interface [[\ArrayAccess]].
1005
     * It is implicitly called when you use something like `unset($model[$offset])`.
1006
     * @param mixed $offset the offset to unset element
1007
     */
1008 1
    public function offsetUnset($offset)
1009
    {
1010 1
        $this->$offset = null;
1011 1
    }
1012
}
1013