Completed
Push — 11139-each-validator-attribute... ( a0e614 )
by Dmitry
08:34
created

Validator::validateAttributes()   B

Complexity

Conditions 9
Paths 32

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 9
Metric Value
dl 0
loc 17
rs 7.756
ccs 15
cts 15
cp 1
cc 9
eloc 11
nc 32
nop 2
crap 9
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\Component;
12
use yii\base\NotSupportedException;
13
14
/**
15
 * Validator is the base class for all validators.
16
 *
17
 * Child classes should override the [[validateValue()]] and/or [[validateAttribute()]] methods to provide the actual
18
 * logic of performing data validation. Child classes may also override [[clientValidateAttribute()]]
19
 * to provide client-side validation support.
20
 *
21
 * Validator declares a set of [[builtInValidators|built-in validators] which can
22
 * be referenced using short names. They are listed as follows:
23
 *
24
 * - `boolean`: [[BooleanValidator]]
25
 * - `captcha`: [[\yii\captcha\CaptchaValidator]]
26
 * - `compare`: [[CompareValidator]]
27
 * - `date`: [[DateValidator]]
28
 * - `default`: [[DefaultValueValidator]]
29
 * - `double`: [[NumberValidator]]
30
 * - `each`: [[EachValidator]]
31
 * - `email`: [[EmailValidator]]
32
 * - `exist`: [[ExistValidator]]
33
 * - `file`: [[FileValidator]]
34
 * - `filter`: [[FilterValidator]]
35
 * - `image`: [[ImageValidator]]
36
 * - `in`: [[RangeValidator]]
37
 * - `integer`: [[NumberValidator]]
38
 * - `match`: [[RegularExpressionValidator]]
39
 * - `required`: [[RequiredValidator]]
40
 * - `safe`: [[SafeValidator]]
41
 * - `string`: [[StringValidator]]
42
 * - `trim`: [[FilterValidator]]
43
 * - `unique`: [[UniqueValidator]]
44
 * - `url`: [[UrlValidator]]
45
 * - `ip`: [[IpValidator]]
46
 *
47
 * @author Qiang Xue <[email protected]>
48
 * @since 2.0
49
 */
50
class Validator extends Component
51
{
52
    /**
53
     * @var array list of built-in validators (name => class or configuration)
54
     */
55
    public static $builtInValidators = [
56
        'boolean' => 'yii\validators\BooleanValidator',
57
        'captcha' => 'yii\captcha\CaptchaValidator',
58
        'compare' => 'yii\validators\CompareValidator',
59
        'date' => 'yii\validators\DateValidator',
60
        'default' => 'yii\validators\DefaultValueValidator',
61
        'double' => 'yii\validators\NumberValidator',
62
        'each' => 'yii\validators\EachValidator',
63
        'email' => 'yii\validators\EmailValidator',
64
        'exist' => 'yii\validators\ExistValidator',
65
        'file' => 'yii\validators\FileValidator',
66
        'filter' => 'yii\validators\FilterValidator',
67
        'image' => 'yii\validators\ImageValidator',
68
        'in' => 'yii\validators\RangeValidator',
69
        'integer' => [
70
            'class' => 'yii\validators\NumberValidator',
71
            'integerOnly' => true,
72
        ],
73
        'match' => 'yii\validators\RegularExpressionValidator',
74
        'number' => 'yii\validators\NumberValidator',
75
        'required' => 'yii\validators\RequiredValidator',
76
        'safe' => 'yii\validators\SafeValidator',
77
        'string' => 'yii\validators\StringValidator',
78
        'trim' => [
79
            'class' => 'yii\validators\FilterValidator',
80
            'filter' => 'trim',
81
            'skipOnArray' => true,
82
        ],
83
        'unique' => 'yii\validators\UniqueValidator',
84
        'url' => 'yii\validators\UrlValidator',
85
        'ip' => 'yii\validators\IpValidator',
86
    ];
87
    /**
88
     * @var array|string attributes to be validated by this validator. For multiple attributes,
89
     * please specify them as an array; for single attribute, you may use either a string or an array.
90
     */
91
    public $attributes = [];
92
    /**
93
     * @var string the user-defined error message. It may contain the following placeholders which
94
     * will be replaced accordingly by the validator:
95
     *
96
     * - `{attribute}`: the label of the attribute being validated
97
     * - `{value}`: the value of the attribute being validated
98
     *
99
     * Note that some validators may introduce other properties for error messages used when specific
100
     * validation conditions are not met. Please refer to individual class API documentation for details
101
     * about these properties. By convention, this property represents the primary error message
102
     * used when the most important validation condition is not met.
103
     */
104
    public $message;
105
    /**
106
     * @var array|string scenarios that the validator can be applied to. For multiple scenarios,
107
     * please specify them as an array; for single scenario, you may use either a string or an array.
108
     */
109
    public $on = [];
110
    /**
111
     * @var array|string scenarios that the validator should not be applied to. For multiple scenarios,
112
     * please specify them as an array; for single scenario, you may use either a string or an array.
113
     */
114
    public $except = [];
115
    /**
116
     * @var boolean whether this validation rule should be skipped if the attribute being validated
117
     * already has some validation error according to some previous rules. Defaults to true.
118
     */
119
    public $skipOnError = true;
120
    /**
121
     * @var boolean whether this validation rule should be skipped if the attribute value
122
     * is null or an empty string.
123
     */
124
    public $skipOnEmpty = true;
125
    /**
126
     * @var boolean whether to enable client-side validation for this validator.
127
     * The actual client-side validation is done via the JavaScript code returned
128
     * by [[clientValidateAttribute()]]. If that method returns null, even if this property
129
     * is true, no client-side validation will be done by this validator.
130
     */
131
    public $enableClientValidation = true;
132
    /**
133
     * @var callable a PHP callable that replaces the default implementation of [[isEmpty()]].
134
     * If not set, [[isEmpty()]] will be used to check if a value is empty. The signature
135
     * of the callable should be `function ($value)` which returns a boolean indicating
136
     * whether the value is empty.
137
     */
138
    public $isEmpty;
139
    /**
140
     * @var callable a PHP callable whose return value determines whether this validator should be applied.
141
     * The signature of the callable should be `function ($model, $attribute)`, where `$model` and `$attribute`
142
     * refer to the model and the attribute currently being validated. The callable should return a boolean value.
143
     *
144
     * This property is mainly provided to support conditional validation on the server side.
145
     * If this property is not set, this validator will be always applied on the server side.
146
     *
147
     * The following example will enable the validator only when the country currently selected is USA:
148
     *
149
     * ```php
150
     * function ($model) {
151
     *     return $model->country == Country::USA;
152
     * }
153
     * ```
154
     *
155
     * @see whenClient
156
     */
157
    public $when;
158
    /**
159
     * @var string a JavaScript function name whose return value determines whether this validator should be applied
160
     * on the client side. The signature of the function should be `function (attribute, value)`, where
161
     * `attribute` is an object describing the attribute being validated (see [[clientValidateAttribute()]])
162
     * and `value` the current value of the attribute.
163
     *
164
     * This property is mainly provided to support conditional validation on the client side.
165
     * If this property is not set, this validator will be always applied on the client side.
166
     *
167
     * The following example will enable the validator only when the country currently selected is USA:
168
     *
169
     * ```php
170
     * function (attribute, value) {
171
     *     return $('#country').val() === 'USA';
172
     * }
173
     * ```
174
     *
175
     * @see when
176
     */
177
    public $whenClient;
178
179
180
    /**
181
     * Creates a validator object.
182
     * @param mixed $type the validator type. This can be a built-in validator name,
183
     * a method name of the model class, an anonymous function, or a validator class name.
184
     * @param \yii\base\Model $model the data model to be validated.
185
     * @param array|string $attributes list of attributes to be validated. This can be either an array of
186
     * the attribute names or a string of comma-separated attribute names.
187
     * @param array $params initial values to be applied to the validator properties
188
     * @return Validator the validator
189
     */
190 22
    public static function createValidator($type, $model, $attributes, $params = [])
191
    {
192 22
        $params['attributes'] = $attributes;
193
194 22
        if ($type instanceof \Closure || $model->hasMethod($type)) {
195
            // method-based validator
196 1
            $params['class'] = __NAMESPACE__ . '\InlineValidator';
197 1
            $params['method'] = $type;
198 1
        } else {
199 22
            if (isset(static::$builtInValidators[$type])) {
200 19
                $type = static::$builtInValidators[$type];
201 19
            }
202 22
            if (is_array($type)) {
203 7
                $params = array_merge($type, $params);
204 7
            } else {
205 17
                $params['class'] = $type;
206
            }
207
        }
208
209 22
        return Yii::createObject($params);
210
    }
211
212
    /**
213
     * @inheritdoc
214
     */
215 316
    public function init()
216
    {
217 316
        parent::init();
218 316
        $this->attributes = (array) $this->attributes;
219 316
        $this->on = (array) $this->on;
220 316
        $this->except = (array) $this->except;
221 316
    }
222
223
    /**
224
     * Validates the specified object.
225
     * @param \yii\base\Model $model the data model being validated
226
     * @param array|null $attributes the list of attributes to be validated.
227
     * Note that if an attribute is not associated with the validator,
228
     * it will be ignored.
229
     * If this parameter is null, every attribute listed in [[attributes]] will be validated.
230
     */
231 8
    public function validateAttributes($model, $attributes = null)
232
    {
233 8
        if (is_array($attributes)) {
234 6
            $attributes = array_intersect($this->attributes, $attributes);
235 6
        } else {
236 4
            $attributes = $this->attributes;
237
        }
238 8
        foreach ($attributes as $attribute) {
0 ignored issues
show
Bug introduced by
The expression $attributes of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
239 7
            $skip = $this->skipOnError && $model->hasErrors($attribute)
240 7
                || $this->skipOnEmpty && $this->isEmpty($model->$attribute);
241 7
            if (!$skip) {
242 7
                if ($this->when === null || call_user_func($this->when, $model, $attribute)) {
243 7
                    $this->validateAttribute($model, $attribute);
244 7
                }
245 7
            }
246 8
        }
247 8
    }
248
249
    /**
250
     * Validates a single attribute.
251
     * Child classes must implement this method to provide the actual validation logic.
252
     * @param \yii\base\Model $model the data model to be validated
253
     * @param string $attribute the name of the attribute to be validated.
254
     */
255 9
    public function validateAttribute($model, $attribute)
256
    {
257 9
        $result = $this->validateValue($model->$attribute);
258 9
        if (!empty($result)) {
259 8
            $this->addError($model, $attribute, $result[0], $result[1]);
260 8
        }
261 9
    }
262
263
    /**
264
     * Validates a given value.
265
     * You may use this method to validate a value out of the context of a data model.
266
     * @param mixed $value the data value to be validated.
267
     * @param string $error the error message to be returned, if the validation fails.
268
     * @return boolean whether the data is valid.
269
     */
270 70
    public function validate($value, &$error = null)
271
    {
272 70
        $result = $this->validateValue($value);
273 65
        if (empty($result)) {
274 54
            return true;
275
        }
276
277 55
        list($message, $params) = $result;
278 55
        $params['attribute'] = Yii::t('yii', 'the input value');
279 55
        $params['value'] = is_array($value) ? 'array()' : $value;
280 55
        $error = Yii::$app->getI18n()->format($message, $params, Yii::$app->language);
281
282 55
        return false;
283
    }
284
285
    /**
286
     * Validates a value.
287
     * A validator class can implement this method to support data validation out of the context of a data model.
288
     * @param mixed $value the data value to be validated.
289
     * @return array|null the error message and the parameters to be inserted into the error message.
290
     * Null should be returned if the data is valid.
291
     * @throws NotSupportedException if the validator does not supporting data validation without a model
292
     */
293 1
    protected function validateValue($value)
294
    {
295 1
        throw new NotSupportedException(get_class($this) . ' does not support validateValue().');
296
    }
297
298
    /**
299
     * Returns the JavaScript needed for performing client-side validation.
300
     *
301
     * You may override this method to return the JavaScript validation code if
302
     * the validator can support client-side validation.
303
     *
304
     * The following JavaScript variables are predefined and can be used in the validation code:
305
     *
306
     * - `attribute`: an object describing the the attribute being validated.
307
     * - `value`: the value being validated.
308
     * - `messages`: an array used to hold the validation error messages for the attribute.
309
     * - `deferred`: an array used to hold deferred objects for asynchronous validation
310
     * - `$form`: a jQuery object containing the form element
311
     *
312
     * The `attribute` object contains the following properties:
313
     * - `id`: a unique ID identifying the attribute (e.g. "loginform-username") in the form
314
     * - `name`: attribute name or expression (e.g. "[0]content" for tabular input)
315
     * - `container`: the jQuery selector of the container of the input field
316
     * - `input`: the jQuery selector of the input field under the context of the form
317
     * - `error`: the jQuery selector of the error tag under the context of the container
318
     * - `status`: status of the input field, 0: empty, not entered before, 1: validated, 2: pending validation, 3: validating
319
     *
320
     * @param \yii\base\Model $model the data model being validated
321
     * @param string $attribute the name of the attribute to be validated.
322
     * @param \yii\web\View $view the view object that is going to be used to render views or view files
323
     * containing a model form with this validator applied.
324
     * @return string the client-side validation script. Null if the validator does not support
325
     * client-side validation.
326
     * @see \yii\widgets\ActiveForm::enableClientValidation
327
     */
328 1
    public function clientValidateAttribute($model, $attribute, $view)
329
    {
330 1
        return null;
331
    }
332
333
    /**
334
     * Returns a value indicating whether the validator is active for the given scenario and attribute.
335
     *
336
     * A validator is active if
337
     *
338
     * - the validator's `on` property is empty, or
339
     * - the validator's `on` property contains the specified scenario
340
     *
341
     * @param string $scenario scenario name
342
     * @return boolean whether the validator applies to the specified scenario.
343
     */
344 12
    public function isActive($scenario)
345
    {
346 12
        return !in_array($scenario, $this->except, true) && (empty($this->on) || in_array($scenario, $this->on, true));
347
    }
348
349
    /**
350
     * Adds an error about the specified attribute to the model object.
351
     * This is a helper method that performs message selection and internationalization.
352
     * @param \yii\base\Model $model the data model being validated
353
     * @param string $attribute the attribute being validated
354
     * @param string $message the error message
355
     * @param array $params values for the placeholders in the error message
356
     */
357 71
    public function addError($model, $attribute, $message, $params = [])
358
    {
359 71
        $params['attribute'] = $model->getAttributeLabel($attribute);
360 71
        if (!isset($params['value'])) {
361 69
            $value = $model->$attribute;
362 69
            $params['value'] = is_array($value) ? 'array()' : $value;
363 69
        }
364 71
        $model->addError($attribute, Yii::$app->getI18n()->format($message, $params, Yii::$app->language));
365 71
    }
366
367
    /**
368
     * Checks if the given value is empty.
369
     * A value is considered empty if it is null, an empty array, or an empty string.
370
     * Note that this method is different from PHP empty(). It will return false when the value is 0.
371
     * @param mixed $value the value to be checked
372
     * @return boolean whether the value is empty
373
     */
374 16
    public function isEmpty($value)
375
    {
376 16
        if ($this->isEmpty !== null) {
377
            return call_user_func($this->isEmpty, $value);
378
        } else {
379 16
            return $value === null || $value === [] || $value === '';
380
        }
381
    }
382
}
383