From 8b3790103e67524e8db23032e217ed5ba831f956 Mon Sep 17 00:00:00 2001 From: mazanax Date: Fri, 18 Aug 2017 14:47:36 +0300 Subject: [PATCH] Create "Luhn" rule --- docs/CreditCard.md | 1 + docs/Luhn.md | 23 +++++++++++++ docs/VALIDATORS.md | 2 ++ library/Exceptions/LuhnException.php | 24 ++++++++++++++ library/Rules/CreditCard.php | 29 +--------------- library/Rules/Imei.php | 8 +---- library/Rules/Luhn.php | 49 ++++++++++++++++++++++++++++ library/Validator.php | 1 + tests/unit/Rules/LuhnTest.php | 43 ++++++++++++++++++++++++ 9 files changed, 145 insertions(+), 35 deletions(-) create mode 100644 docs/Luhn.md create mode 100644 library/Exceptions/LuhnException.php create mode 100644 library/Rules/Luhn.php create mode 100644 tests/unit/Rules/LuhnTest.php diff --git a/docs/CreditCard.md b/docs/CreditCard.md index 8d0997d8..73c2bd2c 100644 --- a/docs/CreditCard.md +++ b/docs/CreditCard.md @@ -44,3 +44,4 @@ See also: - [Bank](Bank.md) - [BankAccount](BankAccount.md) - [Bic](Bic.md) +- [Luhn](Luhn.md) diff --git a/docs/Luhn.md b/docs/Luhn.md new file mode 100644 index 00000000..56fd5a3b --- /dev/null +++ b/docs/Luhn.md @@ -0,0 +1,23 @@ +# Luhn + +- `Luhn()` + +Validate whether a given input is a [Luhn][] number. + +```php +v::luhn()->validate('2222400041240011'); // true +v::luhn()->validate('respect!'); // false +``` + +## Changelog + +Version | Description +--------|------------- + 2.0.0 | Created + +[Luhn]: https://en.wikipedia.org/wiki/Luhn_algorithm +*** +See also: + +- [CreditCard](CreditCard.md) +- [Imei](Imei.md) diff --git a/docs/VALIDATORS.md b/docs/VALIDATORS.md index e4e1de3a..ec1ae82f 100644 --- a/docs/VALIDATORS.md +++ b/docs/VALIDATORS.md @@ -187,6 +187,7 @@ - [Imei](Imei.md) - [Ip](Ip.md) - [Json](Json.md) +- [Luhn](Luhn.md) - [MacAddress](MacAddress.md) - [NfeAccessKey](NfeAccessKey.md) - [Nif](Nif.md) @@ -286,6 +287,7 @@ - [LeapYear](LeapYear.md) - [Length](Length.md) - [Lowercase](Lowercase.md) +- [Luhn](Luhn.md) - [MacAddress](MacAddress.md) - [Max](Max.md) - [Mimetype](Mimetype.md) diff --git a/library/Exceptions/LuhnException.php b/library/Exceptions/LuhnException.php new file mode 100644 index 00000000..2289a037 --- /dev/null +++ b/library/Exceptions/LuhnException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace Respect\Validation\Exceptions; + +class LuhnException extends ValidationException +{ + public static $defaultTemplates = [ + self::MODE_DEFAULT => [ + self::STANDARD => '{{name}} must be a valid Luhn number', + ], + self::MODE_NEGATIVE => [ + self::STANDARD => '{{name}} must not be a valid Luhn number', + ], + ]; +} diff --git a/library/Rules/CreditCard.php b/library/Rules/CreditCard.php index 1880c578..deb64a1d 100644 --- a/library/Rules/CreditCard.php +++ b/library/Rules/CreditCard.php @@ -64,40 +64,13 @@ class CreditCard extends AbstractRule return false; } - if (!$this->verifyMod10($input)) { + if (!(new Luhn())->validate($input)) { return false; } return $this->verifyBrand($input); } - /** - * Returns whether the input matches the Luhn algorithm or not. - * - * @param string $input - * - * @return bool - */ - private function verifyMod10($input) - { - $sum = 0; - $input = strrev($input); - for ($i = 0; $i < mb_strlen($input); ++$i) { - $current = mb_substr($input, $i, 1); - if ($i % 2 == 1) { - $current *= 2; - if ($current > 9) { - $firstDigit = $current % 10; - $secondDigit = ($current - $firstDigit) / 10; - $current = $firstDigit + $secondDigit; - } - } - $sum += $current; - } - - return $sum % 10 == 0; - } - /** * Returns whether the input matches the defined credit card brand or not. * diff --git a/library/Rules/Imei.php b/library/Rules/Imei.php index 36a39778..02fba495 100644 --- a/library/Rules/Imei.php +++ b/library/Rules/Imei.php @@ -33,12 +33,6 @@ class Imei extends AbstractRule return false; } - $sum = 0; - for ($position = 0; $position < (self::IMEI_SIZE - 1); ++$position) { - $number = $numbers[$position] * (($position % 2) + 1); - $sum += ($number % 10) + intval($number / 10); - } - - return (ceil($sum / 10) * 10) - $sum == $numbers[14]; + return (new Luhn())->validate($numbers); } } diff --git a/library/Rules/Luhn.php b/library/Rules/Luhn.php new file mode 100644 index 00000000..bd7d4c93 --- /dev/null +++ b/library/Rules/Luhn.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace Respect\Validation\Rules; + +/** + * @link https://en.wikipedia.org/wiki/Luhn_algorithm + */ +class Luhn extends AbstractRule +{ + /** + * {@inheritdoc} + */ + public function validate($input) + { + if (!(new Digit())->validate($input)) { + return false; + } + + return $this->isValid($input); + } + + private function isValid($input): bool + { + $sum = 0; + $numDigits = strlen($input); + $parity = $numDigits % 2; + for ($i = 0; $i < $numDigits; ++$i) { + $digit = substr($input, $i, 1); + if ($parity == ($i % 2)) { + $digit <<= 1; + if (9 < $digit) { + $digit = $digit - 9; + } + } + $sum += $digit; + } + + return 0 == ($sum % 10); + } +} diff --git a/library/Validator.php b/library/Validator.php index 3fc4fca3..00b5e781 100644 --- a/library/Validator.php +++ b/library/Validator.php @@ -95,6 +95,7 @@ use Respect\Validation\Rules\Key; * @method static Validator leapYear() * @method static Validator length(int $min = null, int $max = null, bool $inclusive = true) * @method static Validator lowercase() + * @method static Validator luhn() * @method static Validator macAddress() * @method static Validator max(mixed $maxValue, bool $inclusive = true) * @method static Validator mimetype(string $mimetype) diff --git a/tests/unit/Rules/LuhnTest.php b/tests/unit/Rules/LuhnTest.php new file mode 100644 index 00000000..1ee4b16e --- /dev/null +++ b/tests/unit/Rules/LuhnTest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the "LICENSE.md" + * file that was distributed with this source code. + */ + +namespace Respect\Validation\Rules; + +/** + * @group rule + * @covers \Respect\Validation\Rules\Luhn + */ +class LuhnTest extends RuleTestCase +{ + public function providerForValidInput() + { + $rule = new Luhn(); + + return [ + [$rule, '2222400041240011'], + [$rule, '340316193809364'], + [$rule, '6011000990139424'], + [$rule, '2223000048400011'], + ]; + } + + public function providerForInvalidInput() + { + $rule = new Luhn(); + + return [ + [$rule, '2222400041240021'], + [$rule, '340316193809334'], + [$rule, '6011000990139421'], + [$rule, '2223000048400010'], + ]; + } +}