Refactor "Between" rule

The "Between" rule was extending the "AllOf" rule and adding "Max" and
"Min" rules to the chain. Because of that, when the rule failed we could
get the "MinException" or the "MaxException" exception, and only if both
failed that we would get the "BetweenException".

With this change it will always get the "BetweenException" which makes
it more explicit.

Also, the "Between" is not using the same standard required in the
Contribution Guidelines.
This commit is contained in:
Henrique Moody 2018-02-27 09:04:23 +01:00
parent 8d44bc3407
commit 60e3fc3740
No known key found for this signature in database
GPG key ID: 221E9281655813A6
16 changed files with 229 additions and 183 deletions

View file

@ -1,9 +1,9 @@
# Between
- `Between(mixed $start, mixed $end)`
- `Between(mixed $start, mixed $end, bool $inclusive)`
- `Between(mixed $minimum, mixed $maximum)`
- `Between(mixed $minimum, mixed $maximum, bool $inclusive)`
Validates ranges. Most simple example:
Validates whether the input is between two other values.
```php
v::intVal()->between(10, 20)->validate(15); // true

View file

@ -13,33 +13,21 @@ declare(strict_types=1);
namespace Respect\Validation\Exceptions;
class BetweenException extends NestedValidationException
/**
* @author Alexandre Gomes Gaigalas <alexandre@gaigalas.net>
* @author Henrique Moody <henriquemoody@gmail.com>
*/
final class BetweenException extends NestedValidationException
{
const BOTH = 0;
const LOWER = 1;
const GREATER = 2;
/**
* {@inheritdoc}
*/
public static $defaultTemplates = [
self::MODE_DEFAULT => [
self::BOTH => '{{name}} must be between {{minValue}} and {{maxValue}}',
self::LOWER => '{{name}} must be greater than {{minValue}}',
self::GREATER => '{{name}} must be lower than {{maxValue}}',
self::STANDARD => '{{name}} must be between {{minimum}} and {{maximum}}',
],
self::MODE_NEGATIVE => [
self::BOTH => '{{name}} must not be between {{minValue}} and {{maxValue}}',
self::LOWER => '{{name}} must not be greater than {{minValue}}',
self::GREATER => '{{name}} must not be lower than {{maxValue}}',
self::STANDARD => '{{name}} must not be between {{minimum}} and {{maximum}}',
],
];
public function chooseTemplate()
{
if (!$this->getParam('minValue')) {
return static::GREATER;
} elseif (!$this->getParam('maxValue')) {
return static::LOWER;
}
return static::BOTH;
}
}

View file

@ -18,10 +18,14 @@ namespace Respect\Validation\Exceptions;
*
* @author Henrique Moody <henriquemoody@gmail.com>
*/
class SizeException extends BetweenException
class SizeException extends NestedValidationException
{
public const BOTH = 0;
public const LOWER = 1;
public const GREATER = 2;
/**
* @var array
* {@inheritdoc}
*/
public static $defaultTemplates = [
self::MODE_DEFAULT => [
@ -35,4 +39,18 @@ class SizeException extends BetweenException
self::GREATER => '{{name}} must not be lower than {{maxSize}}',
],
];
/**
* {@inheritdoc}
*/
public function chooseTemplate(): int
{
if (!$this->getParam('minValue')) {
return static::GREATER;
} elseif (!$this->getParam('maxValue')) {
return static::LOWER;
}
return static::BOTH;
}
}

View file

@ -0,0 +1,53 @@
<?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.
*/
declare(strict_types=1);
namespace Respect\Validation\Helpers;
use DateTimeImmutable;
use DateTimeInterface;
use Exception;
use function is_numeric;
use function is_string;
use function mb_strlen;
/**
* Helps to deal with comparable values.
*
* @author Henrique Moody <henriquemoody@gmail.com>
*/
trait ComparisonHelper
{
/**
* Tries to convert a value into something that can be compared with PHP operators.
*
* @param mixed $value
*
* @return mixed
*/
private function toComparable($value)
{
if ($value instanceof DateTimeInterface || !is_string($value) || is_numeric($value) || empty($value)) {
return $value;
}
if (1 === mb_strlen($value)) {
return $value;
}
try {
return new DateTimeImmutable($value);
} catch (Exception $e) {
return $value;
}
}
}

View file

@ -13,11 +13,12 @@ declare(strict_types=1);
namespace Respect\Validation\Rules;
use DateTimeImmutable;
use Exception;
use Respect\Validation\Helpers\ComparisonHelper;
abstract class AbstractInterval extends AbstractRule
{
use ComparisonHelper { toComparable as protected filterInterval; }
public $interval;
public $inclusive;
@ -26,23 +27,4 @@ abstract class AbstractInterval extends AbstractRule
$this->interval = $interval;
$this->inclusive = $inclusive;
}
protected function filterInterval($value)
{
if (!is_string($value) || is_numeric($value) || empty($value)) {
return $value;
}
if (1 == mb_strlen($value)) {
return $value;
}
try {
return new DateTimeImmutable($value);
} catch (Exception $e) {
// Pokémon Exception Handling
}
return $value;
}
}

View file

@ -14,26 +14,64 @@ declare(strict_types=1);
namespace Respect\Validation\Rules;
use Respect\Validation\Exceptions\ComponentException;
use Respect\Validation\Helpers\ComparisonHelper;
use function Respect\Stringifier\stringify;
class Between extends AllOf
/**
* Validates whether the input is between two other values.
*
* @author Alexandre Gomes Gaigalas <alexandre@gaigalas.net>
* @author Henrique Moody <henriquemoody@gmail.com>
*/
final class Between extends AbstractRule
{
public $minValue;
public $maxValue;
use ComparisonHelper;
public function __construct($min = null, $max = null, $inclusive = true)
/**
* @var mixed
*/
private $minimum;
/**
* @var mixed
*/
private $maximum;
/**
* @var bool
*/
private $inclusive;
/**
* Initializes the rule.
*
* @param mixed $minimum
* @param mixed $maximum
* @param bool $inclusive
*
* @throws ComponentException
*/
public function __construct($minimum, $maximum, bool $inclusive = true)
{
$this->minValue = $min;
$this->maxValue = $max;
if (!is_null($min) && !is_null($max) && $min > $max) {
throw new ComponentException(sprintf('%s cannot be less than %s for validation', $min, $max));
if ($this->toComparable($minimum) >= $this->toComparable($maximum)) {
throw new ComponentException(stringify($minimum).' cannot be less than or equals to '.stringify($maximum));
}
if (!is_null($min)) {
$this->addRule(new Min($min, $inclusive));
}
$this->minimum = $minimum;
$this->maximum = $maximum;
$this->inclusive = $inclusive;
}
if (!is_null($max)) {
$this->addRule(new Max($max, $inclusive));
}
/**
* {@inheritdoc}
*/
public function validate($input): bool
{
$rule = new AllOf(
new Min($this->minimum, $this->inclusive),
new Max($this->maximum, $this->inclusive)
);
return $rule->validate($input);
}
}

View file

@ -32,7 +32,7 @@ use Respect\Validation\Rules\Key;
* @method static Validator attribute(string $reference, Validatable $validator = null, bool $mandatory = true)
* @method static Validator base()
* @method static Validator base64()
* @method static Validator between(mixed $min = null, mixed $max = null, bool $inclusive = true)
* @method static Validator between(mixed $minimum, mixed $maximum, bool $inclusive = true)
* @method static Validator boolType()
* @method static Validator boolVal()
* @method static Validator bsn()

View file

@ -0,0 +1,38 @@
--FILE--
<?php
require 'vendor/autoload.php';
use Respect\Validation\Exceptions\BetweenException;
use Respect\Validation\Exceptions\NestedValidationException;
use Respect\Validation\Validator as v;
try {
v::between(1, 2)->check(0);
} catch (BetweenException $e) {
echo $e->getMessage().PHP_EOL;
}
try {
v::not(v::between('yesterday', 'tomorrow'))->check('today');
} catch (BetweenException $e) {
echo $e->getMessage().PHP_EOL;
}
try {
v::between('a', 'c')->assert('d');
} catch (NestedValidationException $e) {
echo $e->getFullMessage().PHP_EOL;
}
try {
v::not(v::between(-INF, INF))->assert(0);
} catch (NestedValidationException $e) {
echo $e->getFullMessage().PHP_EOL;
}
?>
--EXPECTF--
0 must be between 1 and 2
"today" must not be between "yesterday" and "tomorrow"
- "d" must be between "a" and "c"
- 0 must not be between `-INF` and `INF`

View file

@ -1,15 +0,0 @@
--FILE--
<?php
require 'vendor/autoload.php';
date_default_timezone_set('UTC');
use Respect\Validation\Validator as v;
v::intType()->between(1, 42)->check(2);
v::intType()->between(1, 2)->assert(2);
v::dateTime()->between('1989-12-20', 'tomorrow', false)->assert(new DateTime());
v::stringType()->between('a', 'e', false)->assert('d');
?>
--EXPECTF--

View file

@ -1,15 +0,0 @@
--FILE--
<?php
require 'vendor/autoload.php';
use Respect\Validation\Exceptions\MaxException;
use Respect\Validation\Validator as v;
try {
v::intType()->between(1, 2)->check(42);
} catch (MaxException $e) {
echo $e->getMainMessage().PHP_EOL;
}
?>
--EXPECTF--
42 must be less than or equal to 2

View file

@ -1,15 +0,0 @@
--FILE--
<?php
require 'vendor/autoload.php';
use Respect\Validation\Exceptions\MinException;
use Respect\Validation\Validator as v;
try {
v::intType()->between(1, 2)->check(-42);
} catch (MinException $e) {
echo $e->getMainMessage().PHP_EOL;
}
?>
--EXPECTF--
-42 must be greater than or equal to 1

View file

@ -1,15 +0,0 @@
--FILE--
<?php
require 'vendor/autoload.php';
use Respect\Validation\Exceptions\AllOfException;
use Respect\Validation\Validator as v;
try {
v::between('a', 'b')->assert('c');
} catch (AllOfException $e) {
echo $e->getFullMessage();
}
?>
--EXPECTF--
- "c" must be less than or equal to "b"

View file

@ -12,4 +12,4 @@ try {
}
?>
--EXPECTF--
- "a" must not be less than or equal to "b"
- "a" must not be between "a" and "b"

View file

@ -12,4 +12,4 @@ try {
}
?>
--EXPECTF--
- 41 must not be less than or equal to 42
- 41 must not be between 1 and 42

View file

@ -15,4 +15,4 @@ try {
?>
--EXPECTF--
- "something" is not tasty
- "something" must be greater than or equal to 1
- "something" must be between 1 and 2

View file

@ -14,83 +14,72 @@ declare(strict_types=1);
namespace Respect\Validation\Rules;
use DateTime;
use PHPUnit\Framework\TestCase;
use Respect\Validation\Exceptions\ComponentException;
use Respect\Validation\Test\RuleTestCase;
/**
* @group rule
* @covers \Respect\Validation\Rules\Between
* @covers \Respect\Validation\Exceptions\BetweenException
*/
class BetweenTest extends TestCase
final class BetweenTest extends RuleTestCase
{
public function providerValid()
/**
* {@inheritdoc}
*/
public function providerForValidInput(): array
{
return [
[0, 1, true, 0],
[0, 1, true, 1],
[10, 20, false, 15],
[10, 20, true, 20],
[-10, 20, false, -5],
[-10, 20, false, 0],
['a', 'z', false, 'j'],
[
new DateTime('yesterday'),
new DateTime('tomorrow'),
false,
new DateTime('now'),
],
];
}
public function providerInvalid()
{
return [
[10, 20, false, ''],
[10, 20, true, ''],
[0, 1, false, 0],
[0, 1, false, 1],
[0, 1, false, 2],
[0, 1, false, -1],
[10, 20, false, 999],
[10, 20, false, 20],
[-10, 20, false, -11],
['a', 'j', false, 'z'],
[
new DateTime('yesterday'),
new DateTime('now'),
false,
new DateTime('tomorrow'),
],
[new Between(0, 1, true), 1],
[new Between(0, 1, true), 0],
[new Between(10, 20, false), 15],
[new Between(10, 20, true), 20],
[new Between(-10, 20, false), -5],
[new Between(-10, 20, false), 0],
[new Between('a', 'z', false), 'j'],
[new Between(new DateTime('yesterday'), new DateTime('tomorrow'), false), new DateTime('now')],
];
}
/**
* @dataProvider providerValid
* {@inheritdoc}
*/
public function testValuesBetweenBoundsShouldPass($min, $max, $inclusive, $input): void
public function providerForInvalidInput(): array
{
$o = new Between($min, $max, $inclusive);
self::assertTrue($o->__invoke($input));
$o->assert($input);
$o->check($input);
}
/**
* @dataProvider providerInvalid
* @expectedException \Respect\Validation\Exceptions\BetweenException
*/
public function testValuesOutBoundsShouldRaiseException($min, $max, $inclusive, $input): void
{
$o = new Between($min, $max, $inclusive);
self::assertFalse($o->__invoke($input));
$o->assert($input);
return [
[new Between(10, 20, false), ''],
[new Between(10, 20, true), ''],
[new Between(0, 1, false), 0],
[new Between(0, 1, false), 1],
[new Between(0, 1, false), 2],
[new Between(0, 1, false), -1],
[new Between(10, 20, false), 999],
[new Between(10, 20, false), 20],
[new Between(-10, 20, false), -11],
[new Between('a', 'j', false), 'z'],
[new Between(new DateTime('yesterday'), new DateTime('now'), false), new DateTime('tomorrow')],
];
}
/**
* @test
*
* @expectedException \Respect\Validation\Exceptions\ComponentException
* @expectedExceptionMessage 10 cannot be less than or equals to 5
*/
public function testInvalidConstructionParamsShouldRaiseException(): void
public function minimumValueShouldNotBeGreaterThanMaximumValue(): void
{
$o = new Between(10, 5);
new Between(10, 5);
}
/**
* @test
*
* @expectedException \Respect\Validation\Exceptions\ComponentException
* @expectedExceptionMessage 5 cannot be less than or equals to 5
*/
public function minimumValueShouldNotBeEqualsToMaximumValue(): void
{
new Between(5, 5);
}
}