Completed
Push — fix-wrong-response-code-in-err... ( 60af75...b69ef7 )
by Alexander
79:57 queued 39:19
created

Validator::formatMessage()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 13
ccs 4
cts 4
cp 1
rs 9.2
c 1
b 0
f 0
cc 4
eloc 7
nc 5
nop 2
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\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
 * - `datetime`: [[DateValidator]]
29
 * - `time`: [[DateValidator]]
30
 * - `default`: [[DefaultValueValidator]]
31
 * - `double`: [[NumberValidator]]
32
 * - `each`: [[EachValidator]]
33
 * - `email`: [[EmailValidator]]
34
 * - `exist`: [[ExistValidator]]
35
 * - `file`: [[FileValidator]]
36
 * - `filter`: [[FilterValidator]]
37
 * - `image`: [[ImageValidator]]
38
 * - `in`: [[RangeValidator]]
39
 * - `integer`: [[NumberValidator]]
40
 * - `match`: [[RegularExpressionValidator]]
41
 * - `required`: [[RequiredValidator]]
42
 * - `safe`: [[SafeValidator]]
43
 * - `string`: [[StringValidator]]
44
 * - `trim`: [[FilterValidator]]
45
 * - `unique`: [[UniqueValidator]]
46
 * - `url`: [[UrlValidator]]
47
 * - `ip`: [[IpValidator]]
48
 *
49
 * For more details and usage information on Validator, see the [guide article on validators](guide:input-validation).
50
 *
51
 * @author Qiang Xue <[email protected]>
52
 * @since 2.0
53
 */
54
class Validator extends Component
55
{
56
    /**
57
     * @var array list of built-in validators (name => class or configuration)
58
     */
59
    public static $builtInValidators = [
60
        'boolean' => 'yii\validators\BooleanValidator',
61
        'captcha' => 'yii\captcha\CaptchaValidator',
62
        'compare' => 'yii\validators\CompareValidator',
63
        'date' => 'yii\validators\DateValidator',
64
        'datetime' => [
65
            'class' => 'yii\validators\DateValidator',
66
            'type' => DateValidator::TYPE_DATETIME,
67
        ],
68
        'time' => [
69
            'class' => 'yii\validators\DateValidator',
70
            'type' => DateValidator::TYPE_TIME,
71
        ],
72
        'default' => 'yii\validators\DefaultValueValidator',
73
        'double' => 'yii\validators\NumberValidator',
74
        'each' => 'yii\validators\EachValidator',
75
        'email' => 'yii\validators\EmailValidator',
76
        'exist' => 'yii\validators\ExistValidator',
77
        'file' => 'yii\validators\FileValidator',
78
        'filter' => 'yii\validators\FilterValidator',
79
        'image' => 'yii\validators\ImageValidator',
80
        'in' => 'yii\validators\RangeValidator',
81
        'integer' => [
82
            'class' => 'yii\validators\NumberValidator',
83
            'integerOnly' => true,
84
        ],
85
        'match' => 'yii\validators\RegularExpressionValidator',
86
        'number' => 'yii\validators\NumberValidator',
87
        'required' => 'yii\validators\RequiredValidator',
88
        'safe' => 'yii\validators\SafeValidator',
89
        'string' => 'yii\validators\StringValidator',
90
        'trim' => [
91
            'class' => 'yii\validators\FilterValidator',
92
            'filter' => 'trim',
93
            'skipOnArray' => true,
94
        ],
95
        'unique' => 'yii\validators\UniqueValidator',
96
        'url' => 'yii\validators\UrlValidator',
97
        'ip' => 'yii\validators\IpValidator',
98
    ];
99
    /**
100
     * @var array|string attributes to be validated by this validator. For multiple attributes,
101
     * please specify them as an array; for single attribute, you may use either a string or an array.
102
     */
103
    public $attributes = [];
104
    /**
105
     * @var array cleaned attribute names. Contains attribute names without `!` character at the beginning
106
     * @since 2.0.12
107
     */
108
    private $_attributeNames = [];
109
    /**
110
     * @var string the user-defined error message. It may contain the following placeholders which
111
     * will be replaced accordingly by the validator:
112
     *
113
     * - `{attribute}`: the label of the attribute being validated
114
     * - `{value}`: the value of the attribute being validated
115
     *
116
     * Note that some validators may introduce other properties for error messages used when specific
117
     * validation conditions are not met. Please refer to individual class API documentation for details
118
     * about these properties. By convention, this property represents the primary error message
119
     * used when the most important validation condition is not met.
120
     */
121
    public $message;
122
    /**
123
     * @var array|string scenarios that the validator can be applied to. For multiple scenarios,
124
     * please specify them as an array; for single scenario, you may use either a string or an array.
125
     */
126
    public $on = [];
127
    /**
128
     * @var array|string scenarios that the validator should not be applied to. For multiple scenarios,
129
     * please specify them as an array; for single scenario, you may use either a string or an array.
130
     */
131
    public $except = [];
132
    /**
133
     * @var bool whether this validation rule should be skipped if the attribute being validated
134
     * already has some validation error according to some previous rules. Defaults to true.
135
     */
136
    public $skipOnError = true;
137
    /**
138
     * @var bool whether this validation rule should be skipped if the attribute value
139
     * is null or an empty string.
140
     */
141
    public $skipOnEmpty = true;
142
    /**
143
     * @var bool whether to enable client-side validation for this validator.
144
     * The actual client-side validation is done via the JavaScript code returned
145
     * by [[clientValidateAttribute()]]. If that method returns null, even if this property
146
     * is true, no client-side validation will be done by this validator.
147
     */
148
    public $enableClientValidation = true;
149
    /**
150
     * @var callable a PHP callable that replaces the default implementation of [[isEmpty()]].
151
     * If not set, [[isEmpty()]] will be used to check if a value is empty. The signature
152
     * of the callable should be `function ($value)` which returns a boolean indicating
153
     * whether the value is empty.
154
     */
155
    public $isEmpty;
156
    /**
157
     * @var callable a PHP callable whose return value determines whether this validator should be applied.
158
     * The signature of the callable should be `function ($model, $attribute)`, where `$model` and `$attribute`
159
     * refer to the model and the attribute currently being validated. The callable should return a boolean value.
160
     *
161
     * This property is mainly provided to support conditional validation on the server-side.
162
     * If this property is not set, this validator will be always applied on the server-side.
163
     *
164
     * The following example will enable the validator only when the country currently selected is USA:
165
     *
166
     * ```php
167
     * function ($model) {
168
     *     return $model->country == Country::USA;
169
     * }
170
     * ```
171
     *
172
     * @see whenClient
173
     */
174
    public $when;
175
    /**
176
     * @var string a JavaScript function name whose return value determines whether this validator should be applied
177
     * on the client-side. The signature of the function should be `function (attribute, value)`, where
178
     * `attribute` is an object describing the attribute being validated (see [[clientValidateAttribute()]])
179
     * and `value` the current value of the attribute.
180
     *
181
     * This property is mainly provided to support conditional validation on the client-side.
182
     * If this property is not set, this validator will be always applied on the client-side.
183
     *
184
     * The following example will enable the validator only when the country currently selected is USA:
185
     *
186
     * ```javascript
187
     * function (attribute, value) {
188
     *     return $('#country').val() === 'USA';
189
     * }
190
     * ```
191
     *
192
     * @see when
193
     */
194
    public $whenClient;
195
196
197
    /**
198
     * Creates a validator object.
199
     * @param string|\Closure $type the validator type. This can be either:
200
     *  * a built-in validator name listed in [[builtInValidators]];
201
     *  * a method name of the model class;
202
     *  * an anonymous function;
203
     *  * a validator class name.
204
     * @param \yii\base\Model $model the data model to be validated.
205
     * @param array|string $attributes list of attributes to be validated. This can be either an array of
206
     * the attribute names or a string of comma-separated attribute names.
207
     * @param array $params initial values to be applied to the validator properties.
208
     * @return Validator the validator
209
     */
210 41
    public static function createValidator($type, $model, $attributes, $params = [])
211
    {
212 41
        $params['attributes'] = $attributes;
213
214 41
        if ($type instanceof \Closure || $model->hasMethod($type)) {
215
            // method-based validator
216 3
            $params['class'] = __NAMESPACE__ . '\InlineValidator';
217 3
            $params['method'] = $type;
218 3
        } else {
219 39
            if (isset(static::$builtInValidators[$type])) {
220 35
                $type = static::$builtInValidators[$type];
221 35
            }
222 39
            if (is_array($type)) {
223 11
                $params = array_merge($type, $params);
224 11
            } else {
225 33
                $params['class'] = $type;
226
            }
227
        }
228
229 41
        return Yii::createObject($params);
230
    }
231
232
    /**
233
     * @inheritdoc
234
     */
235 380
    public function init()
236
    {
237 380
        parent::init();
238 380
        $this->attributes = (array) $this->attributes;
239 380
        $this->on = (array) $this->on;
240 380
        $this->except = (array) $this->except;
241 380
        $this->setAttributeNames((array)$this->attributes);
242 380
    }
243
244
    /**
245
     * Validates the specified object.
246
     * @param \yii\base\Model $model the data model being validated
247
     * @param array|null $attributes the list of attributes to be validated.
248
     * Note that if an attribute is not associated with the validator - it will be
249
     * ignored. If this parameter is null, every attribute listed in [[attributes]] will be validated.
250
     */
251 18
    public function validateAttributes($model, $attributes = null)
252
    {
253 18
        if (is_array($attributes)) {
254 16
            $newAttributes = [];
255 16
            foreach ($attributes as $attribute) {
256 16
                if (in_array($attribute, $this->getAttributeNames(), true)) {
257 15
                    $newAttributes[] = $attribute;
258 15
                }
259 16
            }
260 16
            $attributes = $newAttributes;
261 16
        } else {
262 4
            $attributes = $this->getAttributeNames();
263
        }
264
265 18
        foreach ($attributes as $attribute) {
266 17
            $skip = $this->skipOnError && $model->hasErrors($attribute)
267 17
                || $this->skipOnEmpty && $this->isEmpty($model->$attribute);
268 17
            if (!$skip) {
269 12
                if ($this->when === null || call_user_func($this->when, $model, $attribute)) {
270 12
                    $this->validateAttribute($model, $attribute);
271 12
                }
272 12
            }
273 18
        }
274 18
    }
275
276
    /**
277
     * Validates a single attribute.
278
     * Child classes must implement this method to provide the actual validation logic.
279
     * @param \yii\base\Model $model the data model to be validated
280
     * @param string $attribute the name of the attribute to be validated.
281
     */
282 12
    public function validateAttribute($model, $attribute)
283
    {
284 12
        $result = $this->validateValue($model->$attribute);
285 12
        if (!empty($result)) {
286 7
            $this->addError($model, $attribute, $result[0], $result[1]);
287 7
        }
288 12
    }
289
290
    /**
291
     * Validates a given value.
292
     * You may use this method to validate a value out of the context of a data model.
293
     * @param mixed $value the data value to be validated.
294
     * @param string $error the error message to be returned, if the validation fails.
295
     * @return bool whether the data is valid.
296
     */
297 96
    public function validate($value, &$error = null)
298
    {
299 96
        $result = $this->validateValue($value);
300 91
        if (empty($result)) {
301 59
            return true;
302
        }
303
304 79
        list($message, $params) = $result;
305 79
        $params['attribute'] = Yii::t('yii', 'the input value');
306 79
        if (is_array($value)) {
307 12
            $params['value'] = 'array()';
308 79
        } elseif (is_object($value)) {
309 5
            $params['value'] = 'object';
310 5
        } else {
311 69
            $params['value'] = $value;
312
        }
313 79
        $error = $this->formatMessage($message, $params);
314
315 79
        return false;
316
    }
317
318
    /**
319
     * Validates a value.
320
     * A validator class can implement this method to support data validation out of the context of a data model.
321
     * @param mixed $value the data value to be validated.
322
     * @return array|null the error message and the parameters to be inserted into the error message.
323
     * Null should be returned if the data is valid.
324
     * @throws NotSupportedException if the validator does not supporting data validation without a model
325
     */
326 1
    protected function validateValue($value)
327
    {
328 1
        throw new NotSupportedException(get_class($this) . ' does not support validateValue().');
329
    }
330
331
    /**
332
     * Returns the JavaScript needed for performing client-side validation.
333
     *
334
     * Calls [[getClientOptions()]] to generate options array for client-side validation.
335
     *
336
     * You may override this method to return the JavaScript validation code if
337
     * the validator can support client-side validation.
338
     *
339
     * The following JavaScript variables are predefined and can be used in the validation code:
340
     *
341
     * - `attribute`: an object describing the the attribute being validated.
342
     * - `value`: the value being validated.
343
     * - `messages`: an array used to hold the validation error messages for the attribute.
344
     * - `deferred`: an array used to hold deferred objects for asynchronous validation
345
     * - `$form`: a jQuery object containing the form element
346
     *
347
     * The `attribute` object contains the following properties:
348
     * - `id`: a unique ID identifying the attribute (e.g. "loginform-username") in the form
349
     * - `name`: attribute name or expression (e.g. "[0]content" for tabular input)
350
     * - `container`: the jQuery selector of the container of the input field
351
     * - `input`: the jQuery selector of the input field under the context of the form
352
     * - `error`: the jQuery selector of the error tag under the context of the container
353
     * - `status`: status of the input field, 0: empty, not entered before, 1: validated, 2: pending validation, 3: validating
354
     *
355
     * @param \yii\base\Model $model the data model being validated
356
     * @param string $attribute the name of the attribute to be validated.
357
     * @param \yii\web\View $view the view object that is going to be used to render views or view files
358
     * containing a model form with this validator applied.
359
     * @return string the client-side validation script. Null if the validator does not support
360
     * client-side validation.
361
     * @see getClientOptions()
362
     * @see \yii\widgets\ActiveForm::enableClientValidation
363
     */
364 1
    public function clientValidateAttribute($model, $attribute, $view)
365
    {
366 1
        return null;
367
    }
368
369
    /**
370
     * Returns the client-side validation options.
371
     * This method is usually called from [[clientValidateAttribute()]]. You may override this method to modify options
372
     * that will be passed to the client-side validation.
373
     * @param \yii\base\Model $model the model being validated
374
     * @param string $attribute the attribute name being validated
375
     * @return array the client-side validation options
376
     * @since 2.0.11
377
     */
378
    public function getClientOptions($model, $attribute)
379
    {
380
        return [];
381
    }
382
383
    /**
384
     * Returns a value indicating whether the validator is active for the given scenario and attribute.
385
     *
386
     * A validator is active if
387
     *
388
     * - the validator's `on` property is empty, or
389
     * - the validator's `on` property contains the specified scenario
390
     *
391
     * @param string $scenario scenario name
392
     * @return bool whether the validator applies to the specified scenario.
393
     */
394 26
    public function isActive($scenario)
395
    {
396 26
        return !in_array($scenario, $this->except, true) && (empty($this->on) || in_array($scenario, $this->on, true));
397
    }
398
399
    /**
400
     * Adds an error about the specified attribute to the model object.
401
     * This is a helper method that performs message selection and internationalization.
402
     * @param \yii\base\Model $model the data model being validated
403
     * @param string $attribute the attribute being validated
404
     * @param string $message the error message
405
     * @param array $params values for the placeholders in the error message
406
     */
407 92
    public function addError($model, $attribute, $message, $params = [])
408
    {
409 92
        $params['attribute'] = $model->getAttributeLabel($attribute);
410 92
        if (!isset($params['value'])) {
411 92
            $value = $model->$attribute;
412 92
            if (is_array($value)) {
413 28
                $params['value'] = 'array()';
414 92
            } elseif (is_object($value) && !method_exists($value, '__toString')) {
415 1
                $params['value'] = '(object)';
416 1
            } else {
417 90
                $params['value'] = $value;
418
            }
419 92
        }
420 92
        $model->addError($attribute, $this->formatMessage($message, $params));
421 92
    }
422
423
    /**
424
     * Checks if the given value is empty.
425
     * A value is considered empty if it is null, an empty array, or an empty string.
426
     * Note that this method is different from PHP empty(). It will return false when the value is 0.
427
     * @param mixed $value the value to be checked
428
     * @return bool whether the value is empty
429
     */
430 28
    public function isEmpty($value)
431
    {
432 28
        if ($this->isEmpty !== null) {
433
            return call_user_func($this->isEmpty, $value);
434
        } else {
435 28
            return $value === null || $value === [] || $value === '';
436
        }
437
    }
438
439
    /**
440
     * Formats a mesage using the I18N, or simple strtr if `\Yii::$app` is not available.
441
     * @param string $message
442
     * @param array $params
443
     * @since 2.0.12
444 29
     * @return string
445
     */
446 29
    protected function formatMessage($message, $params)
447
    {
448
        if (Yii::$app !== null) {
449
            return \Yii::$app->getI18n()->format($message, $params, Yii::$app->language);
450
        }
451
452
        $placeholders = [];
453
        foreach ((array) $params as $name => $value) {
454
            $placeholders['{' . $name . '}'] = $value;
455
        }
456 380
457 37
        return ($placeholders === []) ? $message : strtr($message, $placeholders);
458 380
    }
459 380
460
    /**
461
     * Returns cleaned attribute names without the `!` character at the beginning
462
     * @return array
463
     * @since 2.0.12
464
     */
465
    public function getAttributeNames()
466
    {
467
        return $this->_attributeNames;
468
    }
469
470
    /**
471
     * Saves attribute names without `!` character at the beginning
472
     * @param array $attributeNames
473
     * @since 2.0.12
474
     */
475
    private function setAttributeNames($attributeNames)
476
    {
477
        $this->_attributeNames = array_map(function($attribute) {
478
            return ltrim($attribute, '!');
479
        }, $attributeNames);
480
    }
481
}
482