Create "Factor" rule

This commit is contained in:
David Meister 2015-09-07 01:37:08 +10:00 committed by Henrique Moody
parent 748b280c34
commit f14e53921c
5 changed files with 328 additions and 0 deletions

19
docs/Factor.md Normal file
View file

@ -0,0 +1,19 @@
# Factor
- `v::factor(int $dividend)`
Validates if the input is a factor of the defined dividend.
```php
v::factor(0)->validate(5); //true
v::factor(4)->validate(2); //true
v::factor(4)->validate(3); //false
```
***
See also:
* [Digit](Digit.md)
* [Finite](Finite.md)
* [Infinite](Infinite.md)
* [Numeric](Numeric.md)

View file

@ -0,0 +1,27 @@
<?php
/*
* This file is part of Respect/Validation.
*
* (c) Alexandre Gomes Gaigalas <alexandre@gaigalas.net>
*
* For the full copyright and license information, please view the "LICENSE.md"
* file that was distributed with this source code.
*/
namespace Respect\Validation\Exceptions;
/**
* @author David Meister <thedavidmeister@gmail.com>
*/
class FactorException extends ValidationException
{
public static $defaultTemplates = array(
self::MODE_DEFAULT => array(
self::STANDARD => '{{name}} must be a factor of {{dividend}}',
),
self::MODE_NEGATIVE => array(
self::STANDARD => '{{name}} must not be a factor of {{dividend}}',
),
);
}

54
library/Rules/Factor.php Normal file
View file

@ -0,0 +1,54 @@
<?php
/*
* This file is part of Respect/Validation.
*
* (c) Alexandre Gomes Gaigalas <alexandre@gaigalas.net>
*
* For the full copyright and license information, please view the "LICENSE.md"
* file that was distributed with this source code.
*/
namespace Respect\Validation\Rules;
use Respect\Validation\Exceptions\ValidationException;
use Respect\Validation\Exceptions\ComponentException;
/**
* @author David Meister <thedavidmeister@gmail.com>
*/
class Factor extends AbstractRule
{
public $dividend;
public function __construct($dividend)
{
if (!is_numeric($dividend) || (int) $dividend != $dividend) {
$message = 'Dividend %s must be an integer';
throw new ComponentException(sprintf($message, ValidationException::stringify($dividend)));
}
$this->dividend = (int) $dividend;
}
public function validate($input)
{
// Every integer is a factor of zero, and zero is the only integer that
// has zero for a factor.
if ($this->dividend === 0) {
return true;
}
// Factors must be integers that are not zero.
if (!is_numeric($input) || (int) $input != $input || $input == 0) {
return false;
}
$input = (int) abs($input);
$dividend = (int) abs($this->dividend);
// The dividend divided by the input must be an integer if input is a
// factor of the dividend.
return is_integer($dividend / $input);
}
}

View file

@ -55,6 +55,7 @@ use Respect\Validation\Rules\Key;
* @method static Validator executable()
* @method static Validator exists()
* @method static Validator extension(string $extension)
* @method static Validator factor(int $dividend)
* @method static Validator false()
* @method static Validator file()
* @method static Validator filterVar(int $filter, mixed $options = null)

View file

@ -0,0 +1,227 @@
<?php
/*
* This file is part of Respect/Validation.
*
* (c) Alexandre Gomes Gaigalas <alexandre@gaigalas.net>
*
* For the full copyright and license information, please view the "LICENSE.md"
* file that was distributed with this source code.
*/
namespace Respect\Validation\Rules;
use Respect\Validation\Exceptions\ValidationException;
/**
* @group rule
* @covers Respect\Validation\Rules\Factor
* @covers Respect\Validation\Exceptions\FactorException
* @author David Meister <thedavidmeister@gmail.com>
*/
class FactorTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider providerForValidFactor
*/
public function testValidFactorShouldReturnTrue($dividend, $input)
{
$min = new Factor($dividend);
$this->assertTrue($min->__invoke($input));
$this->assertTrue($min->check($input));
$this->assertTrue($min->assert($input));
}
/**
* @dataProvider providerForInvalidFactor
*/
public function testInvalidFactorShouldThrowFactorException($dividend, $input)
{
$this->setExpectedException(
'Respect\\Validation\\Exceptions\\FactorException',
ValidationException::stringify($input).' must be a factor of '.$dividend
);
$min = new Factor($dividend);
$this->assertFalse($min->__invoke($input));
$this->assertFalse($min->assert($input));
}
/**
* @dataProvider providerForInvalidFactorDividend
*/
public function testInvalidDividentShouldThrowComponentException($dividend, $input)
{
$this->setExpectedException(
'Respect\\Validation\\Exceptions\\ComponentException',
'Dividend '.ValidationException::stringify($dividend).' must be an integer'
);
// It is enough to simply create a new Factor to trigger the dividend
// exceptions in __construct.
new Factor($dividend);
}
public function providerForValidFactor()
{
$tests = array(
// Run through the first few integers.
array(1, 1),
array(2, 1),
array(2, 2),
array(3, 1),
array(3, 3),
array(4, 1),
array(4, 2),
array(4, 4),
array(5, 1),
array(5, 5),
array(6, 1),
array(6, 2),
array(6, 3),
array(6, 6),
// Zero as a dividend is always a pass.
array(0, 0),
array(0, 1),
array(0, mt_rand()),
);
$tests = $this->generateNegativeCombinations($tests);
$tests = $this->generateStringAndFloatCombinations($tests);
return $tests;
}
public function providerForInvalidFactor()
{
$tests = array(
// Run through the first few integers.
array(3, 2),
array(4, 3),
array(5, 2),
array(5, 3),
array(5, 4),
// Zeros.
array(1, 0),
array(2, 0),
);
$tests = $this->generateNegativeCombinations($tests);
$tests = $this->generateStringAndFloatCombinations($tests);
// Valid (but random) dividends, invalid inputs.
$extra_tests = array_map(
function ($test) {
return array(mt_rand(), $test);
},
$this->thingsThatAreNotIntegers()
);
$tests = array_merge($tests, $extra_tests);
return $tests;
}
public function providerForInvalidFactorDividend()
{
// Invalid dividends, valid (but random) inputs.
$tests = array_map(
function ($test) {
return array($test, mt_rand());
},
$this->thingsThatAreNotIntegers()
);
// Also check for an empty dividend string.
$tests[] = array('', mt_rand());
return $tests;
}
private function thingsThatAreNotIntegers()
{
return array(
0.5,
1.5,
-0.5,
-1.5,
PHP_INT_MAX + 1,
// Non integer values.
$this->randomFloatBeweenZeroAndOne(),
-$this->randomFloatBeweenZeroAndOne(),
'a',
'foo',
// Randomish string.
uniqid('a'),
// Non-scalars.
array(),
new \StdClass(),
new \DateTime(),
null,
true,
false,
);
}
private function randomFloatBeweenZeroAndOne()
{
return mt_rand(1, mt_getrandmax() - 1) / mt_getrandmax();
}
private function generateNegativeCombinations($tests)
{
// Negate all the dividends.
$tests = array_merge(
$tests,
array_map(
function ($test) {
return array(-$test[0], $test[1]);
},
$tests
)
);
// Negate all the inputs.
$tests = array_merge(
$tests,
array_map(
function ($test) {
return array($test[0], -$test[1]);
},
$tests
)
);
return $tests;
}
private function generateStringAndFloatCombinations($tests)
{
$base_tests = $tests;
// Test everything again as a string.
$tests = array_merge(
$tests,
array_map(
function ($test) {
return array((string) $test[0], (string) $test[1]);
},
$base_tests
)
);
// Test everything again as a float.
$tests = array_merge(
$tests,
array_map(
function ($test) {
return array((float) $test[0], (float) $test[1]);
},
$base_tests
)
);
return $tests;
}
}