diff --git a/.gitignore b/.gitignore index f2a1ce6..e214b97 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ index.php nbproject/ +.idea/ +.php_cs.cache # Created by http://www.gitignore.io diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000..a70eec1 --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,14 @@ +setRules([ + '@PSR2' => true, + 'ordered_imports' => true, + 'phpdoc_order' => true, + 'simplified_null_return' => false, + 'no_unused_imports' => true, + ])->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__.'/src') + ->in(__DIR__.'/tests') + ); diff --git a/.travis.yml b/.travis.yml index 678b85e..30db937 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: php php: - - '7.0' - '7.1' - '7.2' before_script: composer install diff --git a/README.md b/README.md index 974d75a..0edc3af 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,13 @@ [![Build Status](https://travis-ci.org/Ekman/Luhn-Algorithm.svg?branch=master)](https://travis-ci.org/Ekman/Luhn-Algorithm) This is an implementation of the Luhn Algorithm in PHP. The Luhn Algorithm is -used to validate things like credit cards and national identifcation numbers. -More information on the algorithm can be found at [Wikipedia](http://en.wikipedia.org/wiki/Luhn_algorithm) +used to validate things like credit cards and national identification numbers. +More information on the algorithm can be found at [Wikipedia](http://en.wikipedia.org/wiki/Luhn_algorithm). ## Installation -Can be installed using composer: +Install with [Composer](https://getcomposer.org/): + ```bash composer require nekman/luhn-algorithm ``` @@ -19,22 +20,21 @@ Use the class like this: ```php use Nekman\LuhnAlgorithm\LuhnAlgorithmFactory; +use Nekman\LuhnAlgorithm\Number; $luhn = LuhnAlgorithmFactory::create(); -if ($luhn->isValid(123456789)) { - // Number is valid. +// Validate a credit card number entered in a form. +$ccNumber = Number::fromString($creditCard); +if ($luhn->isValid($ccNumber)) { + // Credit card number is valid. } -$checkSum = $luhn->calcCheckSum(123456789); - -$checkDigit = $luhn->calcCheckDigit(123456789); -``` +// These methods are used internally by the library. You're free +// to make use of them as well. +$number = new Number(12345); -## Changelog +$checksum = $luhn->calcChecksum($number); -* 4.0.0 - Rewrite of the implementation. -* 3.0.0 - Completely restructured the interface of the library. -* 2.0.1 - Fixed typos in interface. -* 2.0.0 - Added namespace. -* 1.0.0 - Initial release. +$checkDigit = $luhn->calcCheckDigit($number); +``` \ No newline at end of file diff --git a/composer.json b/composer.json index 239e734..dc867e3 100644 --- a/composer.json +++ b/composer.json @@ -31,9 +31,10 @@ } }, "require": { - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.5" + "phpunit/phpunit": "^6.5", + "friendsofphp/php-cs-fixer": "^2.10" } } diff --git a/src/Contract/LuhnAlgorithmInterface.php b/src/Contract/LuhnAlgorithmInterface.php index 83388d4..e80cacd 100644 --- a/src/Contract/LuhnAlgorithmInterface.php +++ b/src/Contract/LuhnAlgorithmInterface.php @@ -1,66 +1,63 @@ -calcChecksum($number); +class LuhnAlgorithm implements LuhnAlgorithmInterface +{ + /** + * {@inheritDoc} + */ + public function isValid(NumberInterface $number): bool + { + if ($number->getCheckDigit() === null) { + throw new \InvalidArgumentException("Check digit cannot be null."); + } - // If the checksum is divisible by 10 it is valid - return ($checksum % 10) === 0; - } + $checksum = $this->calcChecksum($number); + $sum = $checksum + $number->getCheckDigit(); - /** - * {@inheritDoc} - */ - public function calcCheckDigit(int $input): int { - $checkSum = (string) $this->calcChecksum($input . 0); - - // Get the last digit of the checksum - $checkDigit = (int) $checkSum[strlen($checkSum) - 1]; + // If the checksum is divisible by 10 it is valid. + return ($sum % 10) === 0; + } - // If the checkdigit is not 0, then subtract the value from 10 - return $checkDigit === 0 ? $checkDigit : 10 - $checkDigit; - } + /** + * {@inheritDoc} + */ + public function calcCheckDigit(NumberInterface $number): int + { + $checksum = $this->calcChecksum($number); + + // Get the last digit of the checksum. + $checkDigit = $checksum % 10; - /** - * {@inheritDoc} - */ - public function calcChecksum(int $input): int { - $input = (string) $input; - $length = strlen($input); + // If the check digit is not 0, then subtract the value from 10. + return $checkDigit === 0 + ? $checkDigit + : 10 - $checkDigit; + } - $checkSum = 0; + /** + * {@inheritDoc} + */ + public function calcChecksum(NumberInterface $number): int + { + $number = (string) $number->getNumber(); + $nDigits = strlen($number); + $checksum = 0; + $parity = $nDigits % 2; - // Start at the next last digit - for ($i = $length - 2; $i >= 0; $i -= 2) { - // Multiply number with 2 - $tmp = (int) ($input[$i]) * 2; + for ($i = 0; $i < $nDigits; $i++) { + $digit = (int) $number[$i]; - // If a 2 digit number, split and add togheter - if ($tmp > 9) { - $tmp = ($tmp / 10) + ($tmp % 10); - } + // Every other digit, starting from the leftmost, + // shall be doubled. + if (($i % 2) !== $parity) { + $digit *= 2; - // Sum it upp - $checkSum += $tmp; - } + if ($digit > 9) { + $digit -= 9; + } + } - // Start at the next last digit - for ($i = $length - 1; $i >= 0; $i -= 2) { - // Sum it upp - $checkSum += (int) $input[$i]; - } + $checksum += $digit; + } - return $checkSum; - } + return $checksum; + } } diff --git a/src/LuhnAlgorithmFactory.php b/src/LuhnAlgorithmFactory.php index 17eeb13..6a95952 100644 --- a/src/LuhnAlgorithmFactory.php +++ b/src/LuhnAlgorithmFactory.php @@ -1,48 +1,49 @@ -number = $number; + $this->checkDigit = $checkDigit; + } + + /** + * Create a new number from an input that contains the check digit + * already. + * + * @param string $input The input that contains the check digit already. + * + * @return self + */ + public static function fromString(string $input): self + { + $input = (int) preg_replace("/[^\d]/", "", $input); + + // Get the last digit. + $checkDigit = $input % 10; + + // Remove the last digit. + $number = (int) ($input / 10); + + return new self($number, $checkDigit); + } + + /** + * {@inheritdoc} + */ + public function getNumber(): int + { + return $this->number; + } + + /** + * {@inheritdoc} + */ + public function getCheckDigit(): ?int + { + return $this->checkDigit; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return (string) $this->number . (string) $this->checkDigit; + } +} diff --git a/tests/LuhnAlgorithmFactoryTest.php b/tests/LuhnAlgorithmFactoryTest.php index 865607a..f887f3e 100644 --- a/tests/LuhnAlgorithmFactoryTest.php +++ b/tests/LuhnAlgorithmFactoryTest.php @@ -1,36 +1,38 @@ -assertInstanceOf(LuhnAlgorithm::class, LuhnAlgorithmFactory::create()); - } -} +assertInstanceOf(LuhnAlgorithm::class, LuhnAlgorithmFactory::create()); + } +} diff --git a/tests/LuhnAlgorithmTest.php b/tests/LuhnAlgorithmTest.php index b32dd50..de6a036 100644 --- a/tests/LuhnAlgorithmTest.php +++ b/tests/LuhnAlgorithmTest.php @@ -25,60 +25,70 @@ namespace Nekman\LuhnAlgorithm\Test; -use PHPUnit\Framework\TestCase; use Nekman\LuhnAlgorithm\LuhnAlgorithm; +use Nekman\LuhnAlgorithm\Number; +use PHPUnit\Framework\TestCase; -class LuhnAlgorithmTest extends TestCase { - /** - * @var LuhnAlgorithm - */ - private $object; +class LuhnAlgorithmTest extends TestCase +{ + /** + * @var LuhnAlgorithm + */ + private $luhn; - public function setUp() { - parent::setUp(); - - $this->object = new LuhnAlgorithm(); - } + public function setUp() + { + parent::setUp(); + + $this->luhn = new LuhnAlgorithm(); + } - /** - * @dataProvider provideIsValid_success - */ - public function testIsValid_success($number, $expected) { - $this->assertEquals($expected, $this->object->isValid($number)); - } + /** + * @dataProvider provideIsValid_success + */ + public function testIsValid_success($number, $expected) + { + $this->assertEquals($expected, $this->luhn->isValid($number)); + } - public function provideIsValid_success() { - return [ - ["410321-9202", true], - ["410321 - 9202", true], - [4103219202, true], - ]; - } + public function provideIsValid_success() + { + return [ + [new Number(12345, 5), true], + [new Number(410321920, 2), true], + [new Number(3199723370002, 0), true], + ]; + } - /** - * @dataProvider provideCalcChecksum_success - */ - public function testCalcChecksum_success($number, $expected) { - $this->assertEquals($expected, $this->object->calcChecksum($number)); - } + /** + * @dataProvider provideCalcChecksum_success + */ + public function testCalcChecksum_success($number, $expected) + { + $this->assertEquals($expected, $this->luhn->calcChecksum($number)); + } - public function provideCalcChecksum_success() { - return [ - [4103219202, 30], - ]; - } + public function provideCalcChecksum_success() + { + return [ + [new Number(3199723370002), 50], + ]; + } - /** - * @dataProvider provideCalcCheckDigit_success - */ - public function testCalcCheckDigit_success($number, $expected) { - $this->assertEquals($expected, $this->object->calcCheckDigit($number)); - } + /** + * @dataProvider provideCalcCheckDigit_success + */ + public function testCalcCheckDigit_success($number, $expected) + { + $this->assertEquals($expected, $this->luhn->calcCheckDigit($number)); + } - public function provideCalcCheckDigit_success() { - return [ - [12345, 5], - [410321920, 2], - ]; - } + public function provideCalcCheckDigit_success() + { + return [ + [new Number(12345), 5], + [new Number(410321920), 2], + [new Number(3199723370002), 0], + ]; + } } diff --git a/tests/NumberTest.php b/tests/NumberTest.php new file mode 100644 index 0000000..443ab3f --- /dev/null +++ b/tests/NumberTest.php @@ -0,0 +1,63 @@ +assertEquals($expected, Number::fromString($number)); + } + + public function provideWithCheckDigit_success() + { + return [ + ["410321-9202", new Number(410321920, 2)], + [4103219202, new Number(410321920, 2)] + ]; + } + + /** + * @dataProvider provideToString_success + */ + public function testToString_success($number, $expected) + { + $this->assertEquals($expected, (string) $number); + } + + public function provideToString_success() + { + return [ + [new Number(12345, 5), "123455"] + ]; + } +}