Skip to content

Commit cd21b37

Browse files
authored
Rewrite the Luhn Algorithm implementation. (#7)
Rewrite the Luhn Algorithm implementation.
1 parent 754f0d9 commit cd21b37

13 files changed

+504
-266
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
index.php
22
nbproject/
3+
.idea/
4+
.php_cs.cache
35

46
# Created by http://www.gitignore.io
57

.php_cs.dist

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
return PhpCsFixer\Config::create()
4+
->setRules([
5+
'@PSR2' => true,
6+
'ordered_imports' => true,
7+
'phpdoc_order' => true,
8+
'simplified_null_return' => false,
9+
'no_unused_imports' => true,
10+
])->setFinder(
11+
PhpCsFixer\Finder::create()
12+
->in(__DIR__.'/src')
13+
->in(__DIR__.'/tests')
14+
);

.travis.yml

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
language: php
22
php:
3-
- '7.0'
43
- '7.1'
54
- '7.2'
65
before_script: composer install

README.md

+15-15
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
[![Build Status](https://travis-ci.org/Ekman/Luhn-Algorithm.svg?branch=master)](https://travis-ci.org/Ekman/Luhn-Algorithm)
44

55
This is an implementation of the Luhn Algorithm in PHP. The Luhn Algorithm is
6-
used to validate things like credit cards and national identifcation numbers.
7-
More information on the algorithm can be found at [Wikipedia](http://en.wikipedia.org/wiki/Luhn_algorithm)
6+
used to validate things like credit cards and national identification numbers.
7+
More information on the algorithm can be found at [Wikipedia](http://en.wikipedia.org/wiki/Luhn_algorithm).
88

99
## Installation
1010

11-
Can be installed using composer:
11+
Install with [Composer](https://getcomposer.org/):
12+
1213
```bash
1314
composer require nekman/luhn-algorithm
1415
```
@@ -19,22 +20,21 @@ Use the class like this:
1920

2021
```php
2122
use Nekman\LuhnAlgorithm\LuhnAlgorithmFactory;
23+
use Nekman\LuhnAlgorithm\Number;
2224

2325
$luhn = LuhnAlgorithmFactory::create();
2426

25-
if ($luhn->isValid(123456789)) {
26-
// Number is valid.
27+
// Validate a credit card number entered in a form.
28+
$ccNumber = Number::fromString($creditCard);
29+
if ($luhn->isValid($ccNumber)) {
30+
// Credit card number is valid.
2731
}
2832

29-
$checkSum = $luhn->calcCheckSum(123456789);
30-
31-
$checkDigit = $luhn->calcCheckDigit(123456789);
32-
```
33+
// These methods are used internally by the library. You're free
34+
// to make use of them as well.
35+
$number = new Number(12345);
3336

34-
## Changelog
37+
$checksum = $luhn->calcChecksum($number);
3538

36-
* 4.0.0 - Rewrite of the implementation.
37-
* 3.0.0 - Completely restructured the interface of the library.
38-
* 2.0.1 - Fixed typos in interface.
39-
* 2.0.0 - Added namespace.
40-
* 1.0.0 - Initial release.
39+
$checkDigit = $luhn->calcCheckDigit($number);
40+
```

composer.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@
3131
}
3232
},
3333
"require": {
34-
"php": "^7.0"
34+
"php": "^7.1"
3535
},
3636
"require-dev": {
37-
"phpunit/phpunit": "^6.5"
37+
"phpunit/phpunit": "^6.5",
38+
"friendsofphp/php-cs-fixer": "^2.10"
3839
}
3940
}
+63-66
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,63 @@
1-
<?php
2-
3-
/*
4-
* The MIT License (MIT)
5-
*
6-
* Copyright (c) 2014 Niklas Ekman
7-
*
8-
* Permission is hereby granted, free of charge, to any person obtaining a copy of
9-
* this software and associated documentation files (the "Software"), to deal in
10-
* the Software without restriction, including without limitation the rights to
11-
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
12-
* the Software, and to permit persons to whom the Software is furnished to do so,
13-
* subject to the following conditions:
14-
*
15-
* The above copyright notice and this permission notice shall be included in all
16-
* copies or substantial portions of the Software.
17-
*
18-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
20-
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
21-
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22-
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23-
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24-
*/
25-
26-
namespace Nekman\LuhnAlgorithm\Contract;
27-
28-
/**
29-
* Handles the Luhn Algorithm.
30-
*
31-
* @link http://en.wikipedia.org/wiki/Luhn_algorithm
32-
*/
33-
interface LuhnAlgorithmInterface {
34-
/**
35-
* Determine if a number is valid according to the Luhn Algorithm.
36-
*
37-
* @param string $input The number to validate.
38-
*
39-
* @return bool true if number is valid, false otherwise.
40-
*
41-
* @throws \InvalidArgumentException If the input is invalid.
42-
*/
43-
public function isValid(string $input): bool;
44-
45-
/**
46-
* Calculate the check digit for an input.
47-
*
48-
* @param int $input The input to calculate the check digit for.
49-
*
50-
* @return int The check digit.
51-
*
52-
* @throws \InvalidArgumentException If the input is invalid.
53-
*/
54-
public function calcCheckDigit(int $input): int;
55-
56-
/**
57-
* Calulates the checksum for number.
58-
*
59-
* @param int $input The number to calculate the checksum for.
60-
*
61-
* @return int The checksum.
62-
*
63-
* @throws \InvalidArgumentException If the input is invalid.
64-
*/
65-
public function calcChecksum(int $input): int;
66-
}
1+
<?php
2+
3+
/*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2014 Niklas Ekman
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
9+
* this software and associated documentation files (the "Software"), to deal in
10+
* the Software without restriction, including without limitation the rights to
11+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
12+
* the Software, and to permit persons to whom the Software is furnished to do so,
13+
* subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in all
16+
* copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
20+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
21+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24+
*/
25+
26+
namespace Nekman\LuhnAlgorithm\Contract;
27+
28+
/**
29+
* Handles the Luhn Algorithm.
30+
*
31+
* @link http://en.wikipedia.org/wiki/Luhn_algorithm
32+
*/
33+
interface LuhnAlgorithmInterface
34+
{
35+
/**
36+
* Determine if a number is valid according to the Luhn Algorithm.
37+
*
38+
* @param NumberInterface $number The number to validate.
39+
*
40+
* @return bool true if number is valid, false otherwise.
41+
*/
42+
public function isValid(NumberInterface $number): bool;
43+
44+
/**
45+
* Calculate the check digit for an input.
46+
*
47+
* @param NumberInterface $number The number to calculate the check digit for.
48+
*
49+
* @return int The check digit.
50+
*
51+
*/
52+
public function calcCheckDigit(NumberInterface $number): int;
53+
54+
/**
55+
* Calulates the checksum for number.
56+
*
57+
* @param NumberInterface $number The number to calculate the checksum for.
58+
*
59+
* @return int The checksum.
60+
*
61+
*/
62+
public function calcChecksum(NumberInterface $number): int;
63+
}

src/Contract/NumberInterface.php

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* The MIT License (MIT)
5+
*
6+
* Copyright (c) 2014 Niklas Ekman
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy of
9+
* this software and associated documentation files (the "Software"), to deal in
10+
* the Software without restriction, including without limitation the rights to
11+
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
12+
* the Software, and to permit persons to whom the Software is furnished to do so,
13+
* subject to the following conditions:
14+
*
15+
* The above copyright notice and this permission notice shall be included in all
16+
* copies or substantial portions of the Software.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
20+
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
21+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
22+
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23+
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24+
*/
25+
26+
namespace Nekman\LuhnAlgorithm\Contract;
27+
28+
/**
29+
* Describes the structure of a number in the Luhn Algorithm.
30+
*/
31+
interface NumberInterface
32+
{
33+
/**
34+
* Get the number, without check digit.
35+
*
36+
* @return int
37+
*/
38+
public function getNumber(): int;
39+
40+
/**
41+
* Get the check digit for the number.
42+
*
43+
* @return int|null The check digit or null if it has not been calculated yet.
44+
*/
45+
public function getCheckDigit(): ?int;
46+
}

src/LuhnAlgorithm.php

+54-51
Original file line numberDiff line numberDiff line change
@@ -26,68 +26,71 @@
2626
namespace Nekman\LuhnAlgorithm;
2727

2828
use Nekman\LuhnAlgorithm\Contract\LuhnAlgorithmInterface;
29+
use Nekman\LuhnAlgorithm\Contract\NumberInterface;
2930

3031
/**
31-
* Handles the Luhn Algorithm.
32-
*
33-
* @link http://en.wikipedia.org/wiki/Luhn_algorithm
32+
* {@inheritdoc}
3433
*/
35-
class LuhnAlgorithm implements LuhnAlgorithmInterface {
36-
/**
37-
* {@inheritDoc}
38-
*/
39-
public function isValid(string $input): bool {
40-
// Remove everything except digits from the input.
41-
$number = (int) preg_replace("/[^\d]/", "", $input);
42-
43-
$checksum = $this->calcChecksum($number);
34+
class LuhnAlgorithm implements LuhnAlgorithmInterface
35+
{
36+
/**
37+
* {@inheritDoc}
38+
*/
39+
public function isValid(NumberInterface $number): bool
40+
{
41+
if ($number->getCheckDigit() === null) {
42+
throw new \InvalidArgumentException("Check digit cannot be null.");
43+
}
4444

45-
// If the checksum is divisible by 10 it is valid
46-
return ($checksum % 10) === 0;
47-
}
45+
$checksum = $this->calcChecksum($number);
46+
$sum = $checksum + $number->getCheckDigit();
4847

49-
/**
50-
* {@inheritDoc}
51-
*/
52-
public function calcCheckDigit(int $input): int {
53-
$checkSum = (string) $this->calcChecksum($input . 0);
54-
55-
// Get the last digit of the checksum
56-
$checkDigit = (int) $checkSum[strlen($checkSum) - 1];
48+
// If the checksum is divisible by 10 it is valid.
49+
return ($sum % 10) === 0;
50+
}
5751

58-
// If the checkdigit is not 0, then subtract the value from 10
59-
return $checkDigit === 0 ? $checkDigit : 10 - $checkDigit;
60-
}
52+
/**
53+
* {@inheritDoc}
54+
*/
55+
public function calcCheckDigit(NumberInterface $number): int
56+
{
57+
$checksum = $this->calcChecksum($number);
58+
59+
// Get the last digit of the checksum.
60+
$checkDigit = $checksum % 10;
6161

62-
/**
63-
* {@inheritDoc}
64-
*/
65-
public function calcChecksum(int $input): int {
66-
$input = (string) $input;
67-
$length = strlen($input);
62+
// If the check digit is not 0, then subtract the value from 10.
63+
return $checkDigit === 0
64+
? $checkDigit
65+
: 10 - $checkDigit;
66+
}
6867

69-
$checkSum = 0;
68+
/**
69+
* {@inheritDoc}
70+
*/
71+
public function calcChecksum(NumberInterface $number): int
72+
{
73+
$number = (string) $number->getNumber();
74+
$nDigits = strlen($number);
75+
$checksum = 0;
76+
$parity = $nDigits % 2;
7077

71-
// Start at the next last digit
72-
for ($i = $length - 2; $i >= 0; $i -= 2) {
73-
// Multiply number with 2
74-
$tmp = (int) ($input[$i]) * 2;
78+
for ($i = 0; $i < $nDigits; $i++) {
79+
$digit = (int) $number[$i];
7580

76-
// If a 2 digit number, split and add togheter
77-
if ($tmp > 9) {
78-
$tmp = ($tmp / 10) + ($tmp % 10);
79-
}
81+
// Every other digit, starting from the leftmost,
82+
// shall be doubled.
83+
if (($i % 2) !== $parity) {
84+
$digit *= 2;
8085

81-
// Sum it upp
82-
$checkSum += $tmp;
83-
}
86+
if ($digit > 9) {
87+
$digit -= 9;
88+
}
89+
}
8490

85-
// Start at the next last digit
86-
for ($i = $length - 1; $i >= 0; $i -= 2) {
87-
// Sum it upp
88-
$checkSum += (int) $input[$i];
89-
}
91+
$checksum += $digit;
92+
}
9093

91-
return $checkSum;
92-
}
94+
return $checksum;
95+
}
9396
}

0 commit comments

Comments
 (0)