Fix #19735: Fix yii\validators\NumberValidator to use programmable message for the value validation

This commit is contained in:
Bizley
2023-01-13 07:57:03 +01:00
committed by GitHub
parent 55ea8eee1e
commit 581a7b2543
3 changed files with 185 additions and 67 deletions

View File

@ -15,6 +15,7 @@ Yii Framework 2 Change Log
- Chg #19696: Change visibility of `yii\web\View::isPageEnded` to `protected` (lubosdz, samdark)
- Bug #19712: Cast shell_exec() output to string for jsCompressor (impayru)
- Bug #19731: Fix `yii\data\Sort` to generate proper link when multisort is on and attribute has a default sort order set (bizley)
- Bug #19735: Fix `yii\validators\NumberValidator` to use programmable message for the value validation (bizley)
2.0.47 November 18, 2022
------------------------

View File

@ -116,19 +116,19 @@ class NumberValidator extends Validator
protected function validateValue($value)
{
if (is_array($value) && !$this->allowArray) {
return [Yii::t('yii', '{attribute} is invalid.'), []];
return [$this->message, []];
}
$values = !is_array($value) ? [$value] : $value;
foreach ($values as $value) {
if ($this->isNotNumber($value)) {
return [Yii::t('yii', '{attribute} is invalid.'), []];
foreach ($values as $sample) {
if ($this->isNotNumber($sample)) {
return [$this->message, []];
}
$pattern = $this->integerOnly ? $this->integerPattern : $this->numberPattern;
if (!preg_match($pattern, StringHelper::normalizeNumber($value))) {
if (!preg_match($pattern, StringHelper::normalizeNumber($sample))) {
return [$this->message, []];
} elseif ($this->min !== null && $value < $this->min) {
} elseif ($this->min !== null && $sample < $this->min) {
return [$this->tooSmall, ['min' => $this->min]];
} elseif ($this->max !== null && $value > $this->max) {
} elseif ($this->max !== null && $sample > $this->max) {
return [$this->tooBig, ['max' => $this->max]];
}
}

View File

@ -61,12 +61,15 @@ class NumberValidatorTest extends TestCase
public function testEnsureMessageOnInit()
{
$val = new NumberValidator();
$this->assertInternalType('string', $val->message);
$this->assertTrue($val->max === null);
$this->assertSame('{attribute} must be a number.', $val->message);
$this->assertNull($val->max);
$this->assertNull($val->min);
$this->assertNull($val->tooSmall);
$this->assertNull($val->tooBig);
$val = new NumberValidator(['min' => -1, 'max' => 20, 'integerOnly' => true]);
$this->assertInternalType('string', $val->message);
$this->assertInternalType('string', $val->tooSmall);
$this->assertInternalType('string', $val->tooBig);
$this->assertSame('{attribute} must be an integer.', $val->message);
$this->assertSame('{attribute} must be no less than {min}.', $val->tooSmall);
$this->assertSame('{attribute} must be no greater than {max}.', $val->tooBig);
}
public function testValidateValueSimple()
@ -77,39 +80,69 @@ class NumberValidatorTest extends TestCase
$this->assertTrue($val->validate(-20));
$this->assertTrue($val->validate('20'));
$this->assertTrue($val->validate(25.45));
$this->assertFalse($val->validate(false));
$this->assertFalse($val->validate(true));
$this->assertFalse($val->validate(false, $error));
$this->assertSame('the input value must be a number.', $error);
$this->assertFalse($val->validate(true, $error));
$this->assertSame('the input value must be a number.', $error);
$this->assertFalse($val->validate('0x14', $error));
$this->assertSame('the input value must be a number.', $error);
$this->assertTrue($val->validate(0x14));
$this->assertTrue($val->validate('0123'));
$this->assertTrue($val->validate(0123));
$this->assertFalse($val->validate('0b111', $error));
$this->assertSame('the input value must be a number.', $error);
$this->assertTrue($val->validate(0b111));
$this->setPointDecimalLocale();
$this->assertFalse($val->validate('25,45'));
$this->assertFalse($val->validate('25,45', $error));
$this->assertSame('the input value must be a number.', $error);
$this->setCommaDecimalLocale();
$this->assertTrue($val->validate('25,45'));
$this->restoreLocale();
$this->assertFalse($val->validate('12:45'));
$this->assertFalse($val->validate('12:45', $error));
$this->assertSame('the input value must be a number.', $error);
$val = new NumberValidator(['integerOnly' => true]);
$this->assertTrue($val->validate(20));
$this->assertTrue($val->validate(0));
$this->assertFalse($val->validate(25.45));
$this->assertFalse($val->validate(25.45, $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertTrue($val->validate('20'));
$this->assertFalse($val->validate('25,45'));
$this->assertFalse($val->validate('25,45', $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertTrue($val->validate('020'));
$this->assertFalse($val->validate('0x14', $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertTrue($val->validate(0x14));
$this->assertFalse($val->validate('0x14')); // todo check this
$this->assertFalse($val->validate(false));
$this->assertFalse($val->validate(true));
$this->assertTrue($val->validate('0123'));
$this->assertTrue($val->validate(0123));
$this->assertFalse($val->validate('0b111', $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertTrue($val->validate(0b111));
$this->assertFalse($val->validate(false, $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertFalse($val->validate(true, $error));
$this->assertSame('the input value must be an integer.', $error);
}
public function testValidateValueArraySimple()
{
$val = new NumberValidator();
$this->assertFalse($val->validate([20]));
$this->assertFalse($val->validate([0]));
$this->assertFalse($val->validate([-20]));
$this->assertFalse($val->validate(['20']));
$this->assertFalse($val->validate([25.45]));
$this->assertFalse($val->validate([false]));
$this->assertFalse($val->validate([true]));
$this->assertFalse($val->validate([20], $error));
$this->assertSame('the input value must be a number.', $error);
$this->assertFalse($val->validate([0], $error));
$this->assertSame('the input value must be a number.', $error);
$this->assertFalse($val->validate([-20], $error));
$this->assertSame('the input value must be a number.', $error);
$this->assertFalse($val->validate(['20'], $error));
$this->assertSame('the input value must be a number.', $error);
$this->assertFalse($val->validate([25.45], $error));
$this->assertSame('the input value must be a number.', $error);
$this->assertFalse($val->validate([false], $error));
$this->assertSame('the input value must be a number.', $error);
$this->assertFalse($val->validate([true], $error));
$this->assertSame('the input value must be a number.', $error);
$val = new NumberValidator();
$val->allowArray = true;
@ -118,28 +151,38 @@ class NumberValidatorTest extends TestCase
$this->assertTrue($val->validate([-20]));
$this->assertTrue($val->validate(['20']));
$this->assertTrue($val->validate([25.45]));
$this->assertFalse($val->validate([false]));
$this->assertFalse($val->validate([true]));
$this->assertFalse($val->validate([false], $error));
$this->assertSame('the input value must be a number.', $error);
$this->assertFalse($val->validate([true], $error));
$this->assertSame('the input value must be a number.', $error);
$this->setPointDecimalLocale();
$this->assertFalse($val->validate(['25,45']));
$this->assertFalse($val->validate(['25,45'], $error));
$this->assertSame('the input value must be a number.', $error);
$this->setCommaDecimalLocale();
$this->assertTrue($val->validate(['25,45']));
$this->restoreLocale();
$this->assertFalse($val->validate(['12:45']));
$this->assertFalse($val->validate(['12:45'], $error));
$this->assertSame('the input value must be a number.', $error);
$val = new NumberValidator(['integerOnly' => true]);
$val->allowArray = true;
$this->assertTrue($val->validate([20]));
$this->assertTrue($val->validate([0]));
$this->assertFalse($val->validate([25.45]));
$this->assertFalse($val->validate([25.45], $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertTrue($val->validate(['20']));
$this->assertFalse($val->validate(['25,45']));
$this->assertFalse($val->validate(['25,45'], $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertTrue($val->validate(['020']));
$this->assertTrue($val->validate([0x14]));
$this->assertFalse($val->validate(['0x14'])); // todo check this
$this->assertFalse($val->validate([false]));
$this->assertFalse($val->validate([true]));
$this->assertFalse($val->validate(['0x14'], $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertFalse($val->validate([false], $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertFalse($val->validate([true], $error));
$this->assertSame('the input value must be an integer.', $error);
}
public function testValidateValueAdvanced()
@ -148,18 +191,30 @@ class NumberValidatorTest extends TestCase
$this->assertTrue($val->validate('-1.23')); // signed float
$this->assertTrue($val->validate('-4.423e-12')); // signed float + exponent
$this->assertTrue($val->validate('12E3')); // integer + exponent
$this->assertFalse($val->validate('e12')); // just exponent
$this->assertFalse($val->validate('-e3'));
$this->assertFalse($val->validate('-4.534-e-12')); // 'signed' exponent
$this->assertFalse($val->validate('12.23^4')); // expression instead of value
$this->assertFalse($val->validate('e12', $error)); // just exponent
$this->assertSame('the input value must be a number.', $error);
$this->assertFalse($val->validate('-e3', $error));
$this->assertSame('the input value must be a number.', $error);
$this->assertFalse($val->validate('-4.534-e-12', $error)); // 'signed' exponent
$this->assertSame('the input value must be a number.', $error);
$this->assertFalse($val->validate('12.23^4', $error)); // expression instead of value
$this->assertSame('the input value must be a number.', $error);
$val = new NumberValidator(['integerOnly' => true]);
$this->assertFalse($val->validate('-1.23'));
$this->assertFalse($val->validate('-4.423e-12'));
$this->assertFalse($val->validate('12E3'));
$this->assertFalse($val->validate('e12'));
$this->assertFalse($val->validate('-e3'));
$this->assertFalse($val->validate('-4.534-e-12'));
$this->assertFalse($val->validate('12.23^4'));
$this->assertFalse($val->validate('-1.23', $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertFalse($val->validate('-4.423e-12', $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertFalse($val->validate('12E3', $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertFalse($val->validate('e12', $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertFalse($val->validate('-e3', $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertFalse($val->validate('-4.534-e-12', $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertFalse($val->validate('12.23^4', $error));
$this->assertSame('the input value must be an integer.', $error);
}
public function testValidateValueWithLocaleWhereDecimalPointIsComma()
@ -180,28 +235,37 @@ class NumberValidatorTest extends TestCase
$val = new NumberValidator(['min' => 1]);
$this->assertTrue($val->validate(1));
$this->assertFalse($val->validate(-1, $error));
$this->assertContains('the input value must be no less than 1.', $error);
$this->assertFalse($val->validate('22e-12'));
$this->assertSame('the input value must be no less than 1.', $error);
$this->assertFalse($val->validate('22e-12', $error));
$this->assertSame('the input value must be no less than 1.', $error);
$this->assertTrue($val->validate(PHP_INT_MAX + 1));
$val = new NumberValidator(['min' => 1], ['integerOnly' => true]);
$val = new NumberValidator(['min' => 1, 'integerOnly' => true]);
$this->assertTrue($val->validate(1));
$this->assertFalse($val->validate(-1));
$this->assertFalse($val->validate('22e-12'));
$this->assertTrue($val->validate(PHP_INT_MAX + 1));
$this->assertFalse($val->validate(-1, $error));
$this->assertSame('the input value must be no less than 1.', $error);
$this->assertFalse($val->validate('22e-12', $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertFalse($val->validate(PHP_INT_MAX + 1, $error));
$this->assertSame('the input value must be an integer.', $error);
}
public function testValidateValueMax()
{
$val = new NumberValidator(['max' => 1.25]);
$this->assertTrue($val->validate(1));
$this->assertFalse($val->validate(1.5));
$this->assertFalse($val->validate(1.5, $error));
$this->assertSame('the input value must be no greater than 1.25.', $error);
$this->assertTrue($val->validate('22e-12'));
$this->assertTrue($val->validate('125e-2'));
$val = new NumberValidator(['max' => 1.25, 'integerOnly' => true]);
$this->assertTrue($val->validate(1));
$this->assertFalse($val->validate(1.5));
$this->assertFalse($val->validate('22e-12'));
$this->assertFalse($val->validate('125e-2'));
$this->assertFalse($val->validate(1.5, $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertFalse($val->validate('22e-12', $error));
$this->assertSame('the input value must be an integer.', $error);
$this->assertFalse($val->validate('125e-2', $error));
$this->assertSame('the input value must be an integer.', $error);
}
public function testValidateValueRange()
@ -209,13 +273,19 @@ class NumberValidatorTest extends TestCase
$val = new NumberValidator(['min' => -10, 'max' => 20]);
$this->assertTrue($val->validate(0));
$this->assertTrue($val->validate(-10));
$this->assertFalse($val->validate(-11));
$this->assertFalse($val->validate(21));
$this->assertFalse($val->validate(-11, $error));
$this->assertSame('the input value must be no less than -10.', $error);
$this->assertFalse($val->validate(21, $error));
$this->assertSame('the input value must be no greater than 20.', $error);
$val = new NumberValidator(['min' => -10, 'max' => 20, 'integerOnly' => true]);
$this->assertTrue($val->validate(0));
$this->assertFalse($val->validate(-11));
$this->assertFalse($val->validate(22));
$this->assertFalse($val->validate('20e-1'));
$this->assertFalse($val->validate(-11, $error));
$this->assertSame('the input value must be no less than -10.', $error);
$this->assertFalse($val->validate(22, $error));
$this->assertSame('the input value must be no greater than 20.', $error);
$this->assertFalse($val->validate('20e-1', $error));
$this->assertSame('the input value must be an integer.', $error);
}
public function testValidateAttribute()
@ -228,6 +298,7 @@ class NumberValidatorTest extends TestCase
$model->attr_number = '43^32'; //expression
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be a number.', $model->getFirstError('attr_number'));
$val = new NumberValidator(['min' => 10]);
$model = new FakedValidationModel();
$model->attr_number = 10;
@ -236,6 +307,7 @@ class NumberValidatorTest extends TestCase
$model->attr_number = 5;
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be no less than 10.', $model->getFirstError('attr_number'));
$val = new NumberValidator(['max' => 10]);
$model = new FakedValidationModel();
$model->attr_number = 10;
@ -244,6 +316,7 @@ class NumberValidatorTest extends TestCase
$model->attr_number = 15;
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be no greater than 10.', $model->getFirstError('attr_number'));
$val = new NumberValidator(['max' => 10, 'integerOnly' => true]);
$model = new FakedValidationModel();
$model->attr_number = 10;
@ -252,10 +325,12 @@ class NumberValidatorTest extends TestCase
$model->attr_number = 3.43;
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be an integer.', $model->getFirstError('attr_number'));
$val = new NumberValidator(['min' => 1]);
$model = FakedValidationModel::createWithAttributes(['attr_num' => [1, 2, 3]]);
$val->validateAttribute($model, 'attr_num');
$this->assertTrue($model->hasErrors('attr_num'));
$this->assertSame('attr_num must be a number.', $model->getFirstError('attr_num'));
// @see https://github.com/yiisoft/yii2/issues/11672
$model = new FakedValidationModel();
@ -275,6 +350,7 @@ class NumberValidatorTest extends TestCase
$model->attr_number = ['43^32']; //expression
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be a number.', $model->getFirstError('attr_number'));
$val = new NumberValidator(['min' => 10]);
$val->allowArray = true;
$model = new FakedValidationModel();
@ -284,6 +360,7 @@ class NumberValidatorTest extends TestCase
$model->attr_number = [5];
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be no less than 10.', $model->getFirstError('attr_number'));
$val = new NumberValidator(['max' => 10]);
$val->allowArray = true;
$model = new FakedValidationModel();
@ -293,6 +370,7 @@ class NumberValidatorTest extends TestCase
$model->attr_number = [15];
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be no greater than 10.', $model->getFirstError('attr_number'));
$val = new NumberValidator(['max' => 10, 'integerOnly' => true]);
$val->allowArray = true;
$model = new FakedValidationModel();
@ -302,61 +380,73 @@ class NumberValidatorTest extends TestCase
$model->attr_number = [3.43];
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be an integer.', $model->getFirstError('attr_number'));
$val = new NumberValidator(['min' => 1]);
$val->allowArray = true;
$model = FakedValidationModel::createWithAttributes(['attr_num' => [[1], [2], [3]]]);
$val->validateAttribute($model, 'attr_num');
$this->assertTrue($model->hasErrors('attr_num'));
$this->assertSame('attr_num must be a number.', $model->getFirstError('attr_num'));
// @see https://github.com/yiisoft/yii2/issues/11672
$model = new FakedValidationModel();
$model->attr_number = new \stdClass();
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be a number.', $model->getFirstError('attr_number'));
$val = new NumberValidator();
$model = new FakedValidationModel();
$model->attr_number = ['5.5e1'];
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be a number.', $model->getFirstError('attr_number'));
$model->attr_number = ['43^32']; //expression
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be a number.', $model->getFirstError('attr_number'));
$val = new NumberValidator(['min' => 10]);
$model = new FakedValidationModel();
$model->attr_number = [10];
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be a number.', $model->getFirstError('attr_number'));
$model->attr_number = [5];
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be a number.', $model->getFirstError('attr_number'));
$val = new NumberValidator(['max' => 10]);
$model = new FakedValidationModel();
$model->attr_number = [10];
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be a number.', $model->getFirstError('attr_number'));
$model->attr_number = [15];
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be a number.', $model->getFirstError('attr_number'));
$val = new NumberValidator(['max' => 10, 'integerOnly' => true]);
$model = new FakedValidationModel();
$model->attr_number = [10];
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be an integer.', $model->getFirstError('attr_number'));
$model->attr_number = [3.43];
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be an integer.', $model->getFirstError('attr_number'));
$val = new NumberValidator(['min' => 1]);
$model = FakedValidationModel::createWithAttributes(['attr_num' => [[1], [2], [3]]]);
$val->validateAttribute($model, 'attr_num');
$this->assertTrue($model->hasErrors('attr_num'));
$this->assertSame('attr_num must be a number.', $model->getFirstError('attr_num'));
// @see https://github.com/yiisoft/yii2/issues/11672
$model = new FakedValidationModel();
$model->attr_number = new \stdClass();
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertSame('attr_number must be a number.', $model->getFirstError('attr_number'));
}
public function testValidateAttributeWithLocaleWhereDecimalPointIsComma()
@ -376,10 +466,22 @@ class NumberValidatorTest extends TestCase
$this->restoreLocale();
}
public function testEnsureCustomMessageIsSetOnValidateAttribute()
public function testEnsureCustomMessageIsSetOnValidateAttributeGeneral()
{
$val = new NumberValidator(['message' => '{attribute} is not integer.']);
$model = new FakedValidationModel();
$model->attr_number = 'as';
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertCount(1, $model->getErrors('attr_number'));
$msgs = $model->getErrors('attr_number');
$this->assertSame('attr_number is not integer.', $msgs[0]);
}
public function testEnsureCustomMessageIsSetOnValidateAttributeMin()
{
$val = new NumberValidator([
'tooSmall' => '{attribute} is to small.',
'tooSmall' => '{attribute} is too small.',
'min' => 5,
]);
$model = new FakedValidationModel();
@ -388,7 +490,22 @@ class NumberValidatorTest extends TestCase
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertCount(1, $model->getErrors('attr_number'));
$msgs = $model->getErrors('attr_number');
$this->assertSame('attr_number is to small.', $msgs[0]);
$this->assertSame('attr_number is too small.', $msgs[0]);
}
public function testEnsureCustomMessageIsSetOnValidateAttributeMax()
{
$val = new NumberValidator([
'tooBig' => '{attribute} is too big.',
'max' => 5,
]);
$model = new FakedValidationModel();
$model->attr_number = 6;
$val->validateAttribute($model, 'attr_number');
$this->assertTrue($model->hasErrors('attr_number'));
$this->assertCount(1, $model->getErrors('attr_number'));
$msgs = $model->getErrors('attr_number');
$this->assertSame('attr_number is too big.', $msgs[0]);
}
/**