diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..678b85e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: php +php: + - '7.0' + - '7.1' + - '7.2' +before_script: composer install +script: vendor/bin/phpunit diff --git a/README.md b/README.md index 1f770d3..2f91908 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,32 @@ -# Luhn Algorithm in PHP +# Luhn Algorithm + +[![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) ## Installation -Can be installed using composer: - "nekman/luhn-algorithm": "2.*" +Can be installed using composer: +```bash +composer require nekman/luhn-algorithm +``` ## Usage + Use the class like this: - $luhn = new \Nekman\LuhnAlgorithm\LuhnAlgorithm('123456789'); - $luhn->isCompletelyValid(); +```php +use Nekman\LuhnAlgorithm\LuhnAlgorithmFactory; +$luhn = LuhnAlgorithmFactory::create(); -The class contains some static functions as well. This will return the Luhn -checksum of a number: +if ($luhn->isValid(123456789)) { + // Number is valid. +} - $number = '123456789'; - $luhnCheckSum = \Nekman\LuhnAlgorithm\LuhnAlgorithm::calculateChecksum($number); +$checkSum = $luhn->calcCheckSum(123456789); -### Personnummer -If you'd like to validate the input to the class, extend it and do a regex check. -In the file **Personnummer.php**, I have extended the class to make sure that the -input is a valid Swedish national security id. +$checkDigit = $luhn->calcCheckDigit(123456789); +``` diff --git a/composer.json b/composer.json index d03543f..d46d5e2 100644 --- a/composer.json +++ b/composer.json @@ -4,13 +4,11 @@ "description": "Implementation of the Luhn algorithm in PHP. Used in validation of credit card numbers and some national identification numbers.", "keywords": [ "luhn", - "credit card", - "identification number", + "credit", + "card", + "identification", "validation" ], - "require-dev": { - "phpunit/phpunit": "5.1.*" - }, "license": "MIT", "authors": [ { @@ -27,5 +25,16 @@ "psr-4": { "Nekman\\LuhnAlgorithm\\": "src" } + }, + "autoload-dev": { + "psr-4": { + "Nekman\\LuhnAlgorithm\\Test\\": "tests" + } + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5" } } diff --git a/example/Personnummer.example.php b/example/Personnummer.example.php deleted file mode 100644 index 6fda85b..0000000 --- a/example/Personnummer.example.php +++ /dev/null @@ -1,82 +0,0 @@ - - * @version 2014-02-07 - */ -class Personnumer extends LuhnAlgorithm { - - /** - * Validate an input and see if it can be a valid personnumer - * @param type $input - * @return type - */ - public static function isValid($input) { - return preg_match("/^\d{2}[0-1]\d[0-3]\d\s?-?\s?\d{4}$/", trim($input)) === 1; - } - - /** - * The number must be in the format XXXXXX-XXX(D) or - * XXXXXX - XXX(D) or any permutations of those two. If the - * checkdigit (D) is not supplied, it will be calculated - * - * @param string|int $number The personnumer or organizational number - * @param bool $withCheckDigit [Defaults to true]
Is the check digit - * included in $number? - * @throws InvalidArgumentException - */ - public function setNumber($number, $withCheckDigit = true) { - if (!static::isValid($number)) { - throw new \InvalidArgumentException("{$number} is not a valid personnummer"); - } - - parent::setNumber($number, $withCheckDigit); - } - - /** - * Calculate the checksum from a number - * @param string $number Number to calculate checksum of - * @param int $length [Defaults to 0]
Length of $number. Function - * will calculate it if not supplied - * @return int Checksum - * @throws InvalidArgumentException - */ - public static function calculateChecksum($number, $length = 0) { - if (!static::isValid($number)) { - throw new \InvalidArgumentException("{$number} is not a valid personnummer"); - } - - return \LuhnAlgorithm::calculateChecksum($number, $length); - } - -} diff --git a/phpunit.xml b/phpunit.xml index 9eff2a7..f0c31dd 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,5 +1,5 @@ @@ -7,4 +7,4 @@ tests/ - \ No newline at end of file + diff --git a/src/Contract/LuhnAlgorithmInterface.php b/src/Contract/LuhnAlgorithmInterface.php new file mode 100644 index 0000000..83388d4 --- /dev/null +++ b/src/Contract/LuhnAlgorithmInterface.php @@ -0,0 +1,66 @@ + - * @package nekman/luhn-algorithm */ -class LuhnAlgorithm { - - private $number; - private $nDigits; - private $checkDigit; - +class LuhnAlgorithm implements LuhnAlgorithmInterface { /** - * Set number that the instance should handle. - * @param string|int $number Number in string or int format - * @param bool $withCheckDigit [Defaults to true]
Is the check digit - * included in $number? + * {@inheritDoc} */ - function __construct($number, $withCheckDigit = true) { - $this->setNumber($number, $withCheckDigit); - } + public function isValid(string $input): bool { + // Remove everything except digits from the input. + $number = (int) preg_replace("/[^\d]/", "", $input); + + $checksum = $this->calcChecksum($number); - /** - * Validate according to the Luhn Algorithm - * @return bool true if valid - * @throws \InvalidArgumentException If value is null - */ - public function isValid() { - $checksum = self::calculateChecksum($this->number . $this->checkDigit, $this->nDigits + 1); // If the checksum is divisible by 10 it is valid return ($checksum % 10) === 0; } /** - * Calculate the checksum from a number - * @param string $number Number to calculate checksum of - * @param int $length [Defaults to 0]
Length of $number. Function - * will calculate it if not supplied - * @return int Checksum + * {@inheritDoc} */ - public static function calculateChecksum($number, $length = 0) { - $number = strval(self::toInteger($number)); + 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 ($length === 0) { - $length = strlen($number); - } + // If the checkdigit is not 0, then subtract the value from 10 + return $checkDigit === 0 ? $checkDigit : 10 - $checkDigit; + } + + /** + * {@inheritDoc} + */ + public function calcChecksum(int $input): int { + $input = (string) $input; + $length = strlen($input); $checkSum = 0; // Start at the next last digit for ($i = $length - 2; $i >= 0; $i -= 2) { // Multiply number with 2 - $tmp = intval($number[$i]) * 2; + $tmp = (int) ($input[$i]) * 2; // If a 2 digit number, split and add togheter if ($tmp > 9) { @@ -92,95 +85,9 @@ public static function calculateChecksum($number, $length = 0) { // Start at the next last digit for ($i = $length - 1; $i >= 0; $i -= 2) { // Sum it upp - $checkSum += intval($number[$i]); + $checkSum += (int) $input[$i]; } return $checkSum; } - - /** - * Calculate the checkdigit from a number - * @param string $number - * @return int - */ - public static function calculateCheckDigit($number) { - // Get the checksum - $checkSum = strval(self::calculateChecksum($number . 0)); - // Get the last digit of the checksum - $checkDigit = intval($checkSum[strlen($checkSum) - 1]); - - // If the checkdigit is not 0, then subtract the value from 10 - return $checkDigit === 0 ? $checkDigit : 10 - $checkDigit; - } - - /** - * Calculate the checkdigit and verify it - * @return bool true if checkdigit is correct - */ - public function isValidCheckDigit() { - $checkDigit = self::calculateCheckDigit($this->number); - // Validate - return $checkDigit === $this->checkDigit; - } - - /** - * Validate the number by checking both the checksum and check digit - * @return bool true if completely valid - */ - public function isCompletelyValid() { - return $this->isValid() && $this->isValidCheckDigit(); - } - - /** - * Get the number - * @param bool $withCheckDigit If the number should include a checkdigit - * @return string Number - */ - public function getNumber($withCheckDigit = true) { - return $this->number . ($withCheckDigit === true ? $this->checkDigit : ''); - } - - /** - * Get the checkdigit of the number - * @return int Checkdigit - */ - public function getCheckDigit() { - return $this->checkDigit; - } - - /** - * Remove all but numbers from a string - * - * @param string $string String to "convert" to integer - * @return string String containing only numbers - */ - public static function toInteger($string) { - return preg_replace("/[^\d]/", "", $string); - } - - /** - * Set number that the instance should handle. - * @param string|int $number Number in string or int format - * @param bool $withCheckDigit [Defaults to true]
Is the check digit - * included in $number? - */ - public function setNumber($number, $withCheckDigit = true) { - $number = strval(self::toInteger($number)); - $length = strlen($number); - - // If number does not include checkdigit, calculate it! - if (!$withCheckDigit) { - $this->checkDigit = self::calculateCheckDigit($number); - } else { - // Extract check digit from the number - $this->checkDigit = intval($number[$length - 1]); - $number = substr($number, 0, $length - 1); - // Fix length - $length--; - } - - $this->number = $number; - $this->nDigits = $length; - } - } diff --git a/src/LuhnAlgorithmFactory.php b/src/LuhnAlgorithmFactory.php new file mode 100644 index 0000000..17eeb13 --- /dev/null +++ b/src/LuhnAlgorithmFactory.php @@ -0,0 +1,48 @@ +assertInstanceOf(LuhnAlgorithm::class, LuhnAlgorithmFactory::create()); + } +} diff --git a/tests/LuhnAlgorithmTest.php b/tests/LuhnAlgorithmTest.php index 6f66c36..b32dd50 100644 --- a/tests/LuhnAlgorithmTest.php +++ b/tests/LuhnAlgorithmTest.php @@ -1,129 +1,84 @@ object = new LuhnAlgorithm("410321-9202", true); - } - - public function provider() { - $data = array(); - $data[] = array("410321-9202"); - $data[] = array("410321 - 9202"); - $data[] = array(4103219202); - return $data; - } - - /** - * Tears down the fixture, for example, closes a network connection. - * This method is called after a test is executed. - */ - protected function tearDown() { - - } +namespace Nekman\LuhnAlgorithm\Test; - /** - * @covers LuhnAlgorithm::isValid - * @dataProvider provider - */ - public function testIsValid($number) { - $this->object->setNumber($number, true); - $this->assertTrue($this->object->isValid()); - } +use PHPUnit\Framework\TestCase; +use Nekman\LuhnAlgorithm\LuhnAlgorithm; +class LuhnAlgorithmTest extends TestCase { /** - * @covers LuhnAlgorithm::calculateChecksum - * @dataProvider provider + * @var LuhnAlgorithm */ - public function testCalculateChecksum($number) { - $number = LuhnAlgorithm::toInteger($number); - $checkSum = LuhnAlgorithm::calculateChecksum($number); - $this->assertEquals(0, $checkSum % 10); - } + private $object; - /** - * @covers LuhnAlgorithm::calculateCheckDigit - * @dataProvider provider - */ - public function testCalculateCheckDigit($number) { - $number = strval(LuhnAlgorithm::toInteger($number)); - $last = strlen($number) - 1; - - // Check digit is the last number - $checkDigit = $number[$last]; - $number = substr($number, 0, $last); + public function setUp() { + parent::setUp(); - $calcCheckDigit = LuhnAlgorithm::calculateCheckDigit($number); - $this->assertEquals($checkDigit, $calcCheckDigit); + $this->object = new LuhnAlgorithm(); } /** - * @covers LuhnAlgorithm::isValidCheckDigit - * @dataProvider provider + * @dataProvider provideIsValid_success */ - public function testIsValidCheckDigit($number) { - $this->object->setNumber($number, true); - $this->assertTrue($this->object->isValidCheckDigit()); + public function testIsValid_success($number, $expected) { + $this->assertEquals($expected, $this->object->isValid($number)); } - /** - * @covers LuhnAlgorithm::isCompletelyValid - * @dataProvider provider - */ - public function testIsCompletelyValid($number) { - $this->object->setNumber($number, true); - $this->assertTrue($this->object->isCompletelyValid()); + public function provideIsValid_success() { + return [ + ["410321-9202", true], + ["410321 - 9202", true], + [4103219202, true], + ]; } /** - * @covers LuhnAlgorithm::getNumber - * @dataProvider provider + * @dataProvider provideCalcChecksum_success */ - public function testGetNumber($number) { - $this->object->setNumber($number, true); - $number = LuhnAlgorithm::toInteger($number); - $this->assertEquals($number, $this->object->getNumber()); + public function testCalcChecksum_success($number, $expected) { + $this->assertEquals($expected, $this->object->calcChecksum($number)); } - /** - * @covers LuhnAlgorithm::getCheckDigit - * @dataProvider provider - */ - public function testGetCheckDigit($number) { - $number = strval($number); - $checkDigit = $number[strlen($number) - 1]; - $this->object->setNumber($number, true); - $this->assertEquals($this->object->getCheckDigit(), $checkDigit); + public function provideCalcChecksum_success() { + return [ + [4103219202, 30], + ]; } /** - * @covers LuhnAlgorithm::stringToInteger - * @dataProvider provider + * @dataProvider provideCalcCheckDigit_success */ - public function testStringToInteger($number) { - $int = LuhnAlgorithm::toInteger($number); - $this->assertTrue(is_numeric($int)); + public function testCalcCheckDigit_success($number, $expected) { + $this->assertEquals($expected, $this->object->calcCheckDigit($number)); } - /** - * @covers LuhnAlgorithm::setNumber - */ - public function testSetNumber() { - $this->object->setNumber("410321-9202"); + public function provideCalcCheckDigit_success() { + return [ + [12345, 5], + [410321920, 2], + ]; } - }