diff --git a/docs/rules/Decimal.md b/docs/rules/Decimal.md new file mode 100644 index 000000000..8f024ff29 --- /dev/null +++ b/docs/rules/Decimal.md @@ -0,0 +1,53 @@ +# Decimal + +- `Decimal(int $decimals)` + +Validates whether the input matches the expected number of decimals. + +```php +v::decimals(2)->validate('27990.50'); // true +v::decimals(1)->validate('27990.50'); // false +v::decimal(1)->validate(1.5); // true + +``` + +## Known limitations + +When validating float types, it is not possible to determine the amount of +ending zeros and because of that, validations like the ones below will pass. + +```php +v::decimal(1)->validate(1.50); // true +``` + + +## Categorization + +- Numbers + +## Changelog + +Version | Description +--------|------------- + 2.0.0 | Removed support to whitespaces by default + 0.5.0 | Renamed from `Digits` to `Digit` + 0.3.9 | Created as `Digits` + +*** +See also: + +- [Alnum](Alnum.md) +- [Alpha](Alpha.md) +- [Consonant](Consonant.md) +- [CreditCard](CreditCard.md) +- [Factor](Factor.md) +- [Finite](Finite.md) +- [Infinite](Infinite.md) +- [IntType](IntType.md) +- [IntVal](IntVal.md) +- [NotEmoji](NotEmoji.md) +- [NumericVal](NumericVal.md) +- [Regex](Regex.md) +- [Uuid](Uuid.md) +- [Vowel](Vowel.md) +- [Xdigit](Xdigit.md) diff --git a/library/ChainedValidator.php b/library/ChainedValidator.php index 1fb4a0f71..d3e206013 100644 --- a/library/ChainedValidator.php +++ b/library/ChainedValidator.php @@ -101,6 +101,8 @@ public function date(string $format = 'Y-m-d'): ChainedValidator; public function dateTime(?string $format = null): ChainedValidator; + public function decimal(int $decimals): ChainedValidator; + public function digit(string ...$additionalChars): ChainedValidator; public function directory(): ChainedValidator; diff --git a/library/Exceptions/DecimalException.php b/library/Exceptions/DecimalException.php new file mode 100644 index 000000000..464d2b9cb --- /dev/null +++ b/library/Exceptions/DecimalException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Respect\Validation\Exceptions; + +/** + * @author Henrique Moody + */ +final class DecimalException extends ValidationException +{ + /** + * {@inheritDoc} + */ + protected $defaultTemplates = [ + self::MODE_DEFAULT => [ + self::STANDARD => '{{name}} must have {{decimals}} decimals', + ], + self::MODE_NEGATIVE => [ + self::STANDARD => '{{name}} must not have {{decimals}} decimals', + ], + ]; +} diff --git a/library/Rules/Decimal.php b/library/Rules/Decimal.php new file mode 100644 index 000000000..f0ca28477 --- /dev/null +++ b/library/Rules/Decimal.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Respect\Validation\Rules; + +use function is_numeric; +use function is_string; +use function number_format; +use function preg_replace; +use function var_export; + +/** + * Validates the decimal + * + * @author Henrique Moody + */ +final class Decimal extends AbstractRule +{ + /** + * @var int + */ + private $decimals; + + public function __construct(int $decimals) + { + $this->decimals = $decimals; + } + + /** + * {@inheritDoc} + */ + public function validate($input): bool + { + if (!is_numeric($input)) { + return false; + } + + return $this->toFormattedString($input) === $this->toRawString($input); + } + + /** + * @param mixed $input + */ + private function toRawString($input): string + { + if (is_string($input)) { + return $input; + } + + return var_export($input, true); + } + + /** + * @param mixed $input + */ + private function toFormattedString($input): string + { + $formatted = number_format((float) $input, $this->decimals, '.', ''); + if (is_string($input)) { + return $formatted; + } + + return preg_replace('/^(\d.\d)0*/', '$1', $formatted); + } +} diff --git a/library/StaticValidator.php b/library/StaticValidator.php index 566fb4606..f5adf5f26 100644 --- a/library/StaticValidator.php +++ b/library/StaticValidator.php @@ -101,6 +101,8 @@ public static function date(string $format = 'Y-m-d'): ChainedValidator; public static function dateTime(?string $format = null): ChainedValidator; + public static function decimal(int $decimals): ChainedValidator; + public static function digit(string ...$additionalChars): ChainedValidator; public static function directory(): ChainedValidator; diff --git a/tests/integration/rules/decimal.phpt b/tests/integration/rules/decimal.phpt new file mode 100644 index 000000000..8117aefb4 --- /dev/null +++ b/tests/integration/rules/decimal.phpt @@ -0,0 +1,42 @@ +--CREDITS-- +Henrique Moody +--FILE-- +check(0.1234); +} catch (DecimalException $exception) { + echo $exception->getMessage() . PHP_EOL; +} + +try { + v::decimal(2)->assert(0.123); +} catch (NestedValidationException $exception) { + echo $exception->getFullMessage() . PHP_EOL; +} + +try { + v::not(v::decimal(5))->check(0.12345); +} catch (DecimalException $exception) { + echo $exception->getMessage() . PHP_EOL; +} + +try { + v::not(v::decimal(2))->assert(0.34); +} catch (NestedValidationException $exception) { + echo $exception->getFullMessage() . PHP_EOL; +} +?> +--EXPECT-- +0.1234 must have 3 decimals +- 0.123 must have 2 decimals +0.12345 must not have 5 decimals +- 0.34 must not have 2 decimals diff --git a/tests/unit/Rules/DecimalTest.php b/tests/unit/Rules/DecimalTest.php new file mode 100644 index 000000000..a60f4dfb7 --- /dev/null +++ b/tests/unit/Rules/DecimalTest.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Respect\Validation\Rules; + +use Respect\Validation\Test\RuleTestCase; +use stdClass; + +use function acos; +use function sqrt; + +use const NAN; + +/** + * @group rule + * + * @covers \Respect\Validation\Rules\Decimal + * + * @author Henrique Moody + * @author Ismael Elias + * @author Vitaliy + */ +final class DecimalTest extends RuleTestCase +{ + /** + * {@inheritDoc} + */ + public function providerForValidInput(): array + { + return [ + [new Decimal(0), 1], + [new Decimal(1), 1.0], + [new Decimal(2), 1.00], + [new Decimal(3), 1.000], + [new Decimal(2), 1.000], + [new Decimal(1), 1.000], + [new Decimal(2), '27990.50'], + [new Decimal(1), 1.1], + [new Decimal(1), '1.3'], + [new Decimal(1), 1.50], + [new Decimal(3), '1.000'], + [new Decimal(3), 123456789.001], + ]; + } + + /** + * {@inheritDoc} + */ + public function providerForInvalidInput(): array + { + return [ + [new Decimal(1), '1.50'], + [new Decimal(1), '27990.50'], + [new Decimal(0), 2.0], + [new Decimal(0), acos(1.01)], + [new Decimal(0), sqrt(-1)], + [new Decimal(0), NAN], + [new Decimal(0), -NAN], + [new Decimal(0), false], + [new Decimal(0), true], + [new Decimal(0), []], + [new Decimal(0), new stdClass()], + ]; + } +}