Skip to content

Commit

Permalink
feat(Validator): Number 增加总长度和小数长度选项
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Number 规则传入 NAN 将返回 false
  • Loading branch information
twinh committed Nov 24, 2020
1 parent a0d68e7 commit 22e32eb
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 5 deletions.
112 changes: 111 additions & 1 deletion lib/Validator/Number.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,128 @@ class Number extends BaseValidator
{
protected $notNumberMessage = '%name% must be valid number';

protected $scaleMessage = '%name% can have at most %scale% decimal(s)';

protected $lessThanMessage = '%name% must be greater than or equal to %value%';

protected $greaterThanMessage = '%name% must be less than or equal to %value%';

protected $precisionMessage = '%name% can have';

protected $negativeMessage = '%name% must not be number';

/**
* The number of digits in the input
*
* @var int|null
*/
protected $precision;

/**
* The number of digits after the decimal point
*
* @var int|null
*/
protected $scale;

/**
* Whether the input must be greater than or equal to 0
*
* To enable this option, use `uNumber` rule instead.
*
* @var bool
*/
protected $unsigned = false;

/**
* The calculated number for $this->lessThanMessage and $this->greaterThanMessage
*
* @var string
* @internal
*/
protected $value;

/**
* {@inheritdoc}
*/
public function __invoke($input, int $precision = null, int $scale = null)
{
if (null !== $precision && $scale > $precision) {
throw new \InvalidArgumentException('Precision must be greater than or equals scale');
}

null !== $precision && $this->storeOption('precision', $precision);
null !== $scale && $this->storeOption('scale', $scale);

return $this->isValid($input);
}

/**
* {@inheritdoc}
*/
protected function doValidate($input)
{
if (!is_numeric($input)) {
if (!is_numeric($input) || is_nan($input)) {
$this->addError('notNumber');
return false;
}

if (null !== $this->scale && $this->scale < $this->getDecimalCount($input)) {
$this->addError('scale');
return false;
}

if (null === $this->precision) {
return true;
}

[$min, $max] = $this->getRange();
if ($input > $max) {
$this->value = $max;
$this->addError('greaterThan');
return false;
}

if ($input < $min) {
$this->value = $min;
$this->addError('lessThan');
return false;
}

return true;
}

/**
* @return int[]
*/
private function getRange()
{
$max = (str_repeat(9, $this->precision - $this->scale) ?: '0')
. ($this->scale ? ('.' . str_repeat(9, $this->scale)) : '');

$min = $this->unsigned ? 0 : -$max;

return [$min, $max];
}

/**
* @param mixed $number
* @return int
* @link https://stackoverflow.com/a/12525070
*/
private function getDecimalCount($number)
{
$precision = 0;
while (true) {
if ((string) $number === (string) round($number)) {
break;
}
if (is_infinite($number)) {
break;
}
$number *= 10;
$precision++;
}
return $precision;
}
}
1 change: 1 addition & 0 deletions lib/Validator/i18n/zh-CN.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@
// number
'%name% must be valid number' => '%name%必须是有效的数字',
'%name% must not be number' => '%name%不能是数字',
'%name% can have at most %scale% decimal(s)' => '%name%最多只能有%scale%位小数',

// naturalNumber
'%name% must be positive integer or zero' => '%name%必须是大于等于0的整数',
Expand Down
85 changes: 81 additions & 4 deletions tests/unit/Validator/NumberTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,33 @@
*/
final class NumberTest extends TestCase
{
public function setUp(): void
{
parent::setUp();

wei()->t->setLocale('en');
}

/**
* @dataProvider providerForNumber
* @param mixed $input
* @param int|null $precision
* @param int|null $scale
*/
public function testNumber($input)
public function testNumber($input, int $precision = null, int $scale = null)
{
$this->assertTrue($this->isNumber($input));
$this->assertTrue($this->isNumber($input, $precision, $scale));
}

/**
* @dataProvider providerForNotNumber
* @param mixed $input
* @param int|null $precision
* @param int|null $scale
*/
public function testNotNumber($input)
public function testNotNumber($input, int $precision = null, int $scale = null)
{
$this->assertFalse($this->isNumber($input));
$this->assertFalse($this->isNumber($input, $precision, $scale));
}

public function providerForNumber()
Expand All @@ -35,6 +46,24 @@ public function providerForNumber()
['123456789'],
['1.1'],
[2.0],
['1.0', null, 1],
['0.1', null, 2],
['0.12', null, 2],
['1.234', null, 3],
['-1', null, 0],
[(0.1 + 0.7) * 10, null, 0],
[1.1E-10, null, 11],
[INF, null, 0],
[-INF, null, 0],
[1, 1],
[10, 2],
[100, 3],
[0.1, 1, 1],
[0.11, 2, 2],
[1, 1, 0],
[1.1, 2, 1],
[10.1, 3, 1],
[100.1, 4, 1],
];
}

Expand All @@ -44,6 +73,54 @@ public function providerForNotNumber()
['012345-1234567890'],
['not number'],
['0.1a'],
['1.234', null, 2],
['1.234', null, 0],
[1.1E-10, null, 10],
[10, 1],
[100, 2],
[1000, 3],
[0.1, 0, 0],
[0.11, 2, 1],
[1.1, 1, 1],
[10.1, 2, 1],
[100.1, 3, 1],
[1000.1, 4, 1],
[NAN],
[INF, 1, null],
[-INF, 1, null],
[INF, 100, null],
[-INF, 100, null],
];
}

public function testInvalidArgument()
{
$this->expectExceptionObject(new \InvalidArgumentException('Precision must be greater than or equals scale'));

$this->isNumber(1, 1, 2);
}

public function testScaleMessage()
{
$this->assertFalse($this->isNumber(0.11, 2, 1));
$this->assertSame('This value can only have at most 1 decimals', $this->isNumber->getFirstMessage());
}

public function testGreaterThanMessage()
{
$this->assertFalse($this->isNumber(10, 2, 1));
$this->assertSame('This value must be less than or equal to 9.9', $this->isNumber->getFirstMessage());

$this->assertFalse($this->isNumber(10, 1, 0));
$this->assertSame('This value must be less than or equal to 9', $this->isNumber->getFirstMessage());
}

public function testLessThanMessage()
{
$this->assertFalse($this->isNumber(-10, 2, 1));
$this->assertSame('This value must be greater than or equal to -9.9', $this->isNumber->getFirstMessage());

$this->assertFalse($this->isNumber(-10, 1, 0));
$this->assertSame('This value must be greater than or equal to -9', $this->isNumber->getFirstMessage());
}
}

0 comments on commit 22e32eb

Please sign in to comment.