Completed
Push — master ( 47191d...6e3057 )
by Dmitry
13:43
created

EachValidator::validateAttribute()   D

Complexity

Conditions 9
Paths 12

Size

Total Lines 40
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 9.0294

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 40
ccs 26
cts 28
cp 0.9286
rs 4.909
cc 9
eloc 28
nc 12
nop 2
crap 9.0294
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\validators;
9
10
use Yii;
11
use yii\base\InvalidConfigException;
12
use yii\base\Model;
13
14
/**
15
 * EachValidator validates an array by checking each of its elements against an embedded validation rule.
16
 *
17
 * ```php
18
 * class MyModel extends Model
19
 * {
20
 *     public $categoryIDs = [];
21
 *
22
 *     public function rules()
23
 *     {
24
 *         return [
25
 *             // checks if every category ID is an integer
26
 *             ['categoryIDs', 'each', 'rule' => ['integer']],
27
 *         ]
28
 *     }
29
 * }
30
 * ```
31
 *
32
 * > Note: This validator will not work with inline validation rules in case of usage outside the model scope,
33
 *   e.g. via [[validate()]] method.
34
 *
35
 * > Note: EachValidator is meant to be used only in basic cases, you should consider usage of tabular input,
36
 *   using several models for the more complex case.
37
 *
38
 * @author Paul Klimov <[email protected]>
39
 * @since 2.0.4
40
 */
41
class EachValidator extends Validator
42
{
43
    /**
44
     * @var array|Validator definition of the validation rule, which should be used on array values.
45
     * It should be specified in the same format as at [[\yii\base\Model::rules()]], except it should not
46
     * contain attribute list as the first element.
47
     * For example:
48
     *
49
     * ```php
50
     * ['integer']
51
     * ['match', 'pattern' => '/[a-z]/is']
52
     * ```
53
     *
54
     * Please refer to [[\yii\base\Model::rules()]] for more details.
55
     */
56
    public $rule;
57
    /**
58
     * @var bool whether to use error message composed by validator declared via [[rule]] if its validation fails.
59
     * If enabled, error message specified for this validator itself will appear only if attribute value is not an array.
60
     * If disabled, own error message value will be used always.
61
     */
62
    public $allowMessageFromRule = true;
63
    /**
64
     * @var bool whether to stop validation once first error among attribute value elements is detected.
65
     * When enabled validation will produce single error message on attribute, when disabled - multiple
66
     * error messages mya appear: one per each invalid value.
67
     * Note that this option will affect only [[validateAttribute()]] value, while [[validateValue()]] will
68
     * not be affected.
69
     * @since 2.0.11
70
     */
71
    public $stopOnFirstError = true;
72
73
    /**
74
     * @var Validator validator instance.
75
     */
76
    private $_validator;
77
78
79
    /**
80
     * {@inheritdoc}
81
     */
82 41
    public function init()
83
    {
84 41
        parent::init();
85 41
        if ($this->message === null) {
86 41
            $this->message = Yii::t('yii', '{attribute} is invalid.');
87
        }
88 41
    }
89
90
    /**
91
     * Returns the validator declared in [[rule]].
92
     * @param Model|null $model model in which context validator should be created.
93
     * @return Validator the declared validator.
94
     */
95 9
    private function getValidator($model = null)
96
    {
97 9
        if ($this->_validator === null) {
98 9
            $this->_validator = $this->createEmbeddedValidator($model);
99
        }
100
101 9
        return $this->_validator;
102
    }
103
104
    /**
105
     * Creates validator object based on the validation rule specified in [[rule]].
106
     * @param Model|null $model model in which context validator should be created.
107
     * @throws \yii\base\InvalidConfigException
108
     * @return Validator validator instance
109
     */
110 9
    private function createEmbeddedValidator($model)
111
    {
112 9
        $rule = $this->rule;
113 9
        if ($rule instanceof Validator) {
114
            return $rule;
115 9
        } elseif (is_array($rule) && isset($rule[0])) { // validator type
116 9
            if (!is_object($model)) {
117 3
                $model = new Model(); // mock up context model
118
            }
119
120 9
            return Validator::createValidator($rule[0], $model, $this->attributes, array_slice($rule, 1));
121
        }
122
123
        throw new InvalidConfigException('Invalid validation rule: a rule must be an array specifying validator type.');
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129 7
    public function validateAttribute($model, $attribute)
130
    {
131 7
        $value = $model->$attribute;
132 7
        if (!is_array($value) && !$value instanceof \ArrayAccess) {
133
            $this->addError($model, $attribute, $this->message, []);
134
            return;
135
        }
136
137 7
        $validator = $this->getValidator($model); // ensure model context while validator creation
138
139 7
        $detectedErrors = $model->getErrors($attribute);
140 7
        $filteredValue = $model->$attribute;
141 7
        foreach ($value as $k => $v) {
142 7
            $model->clearErrors($attribute);
143 7
            $model->$attribute = $v;
144 7
            if (!$validator->skipOnEmpty || !$validator->isEmpty($v)) {
145 7
                $validator->validateAttribute($model, $attribute);
146
            }
147 7
            $filteredValue[$k] = $model->$attribute;
148 7
            if ($model->hasErrors($attribute)) {
149 5
                if ($this->allowMessageFromRule) {
150 5
                    $validationErrors = $model->getErrors($attribute);
151 5
                    $detectedErrors = array_merge($detectedErrors, $validationErrors);
152
                } else {
153 2
                    $model->clearErrors($attribute);
154 2
                    $this->addError($model, $attribute, $this->message, ['value' => $v]);
155 2
                    $detectedErrors[] = $model->getFirstError($attribute);
156
                }
157 5
                $model->$attribute = $value;
158
159 5
                if ($this->stopOnFirstError) {
160 7
                    break;
161
                }
162
            }
163
        }
164
165 7
        $model->$attribute = $filteredValue;
166 7
        $model->clearErrors($attribute);
167 7
        $model->addErrors([$attribute => $detectedErrors]);
168 7
    }
169
170
    /**
171
     * {@inheritdoc}
172
     */
173 4
    protected function validateValue($value)
174
    {
175 4
        if (!is_array($value) && !$value instanceof \ArrayAccess) {
176 1
            return [$this->message, []];
177
        }
178
179 4
        $validator = $this->getValidator();
180 4
        foreach ($value as $v) {
181 4
            if ($validator->skipOnEmpty && $validator->isEmpty($v)) {
182 1
                continue;
183
            }
184 4
            $result = $validator->validateValue($v);
185 4
            if ($result !== null) {
186 2
                if ($this->allowMessageFromRule) {
187 2
                    $result[1]['value'] = $v;
188 2
                    return $result;
189
                }
190
191 3
                return [$this->message, ['value' => $v]];
192
            }
193
        }
194
195 4
        return null;
196
    }
197
}
198