From 33bbb9bc78fc3f33eeab8a2e4467b4fc6b71da02 Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 1 Sep 2014 17:20:05 +0200 Subject: [PATCH 1/6] Add Luhn util class --- src/Faker/Util/Luhn.php | 56 ++++++++++++++++++++++++++++++++++++ test/Faker/Util/LuhnTest.php | 53 ++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 src/Faker/Util/Luhn.php create mode 100644 test/Faker/Util/LuhnTest.php diff --git a/src/Faker/Util/Luhn.php b/src/Faker/Util/Luhn.php new file mode 100644 index 0000000000..51a92a5818 --- /dev/null +++ b/src/Faker/Util/Luhn.php @@ -0,0 +1,56 @@ + $digit) { + if ($i % 2 !== 0) { + $digit = $digit * 2; + if ($digit > 9) { + $digit = (int) ($digit / 10) + $digit % 10; + } + } + $sum += $digit; + } + + return $sum % 10; + } + + /** + * @return string + */ + public static function computeCheckDigit($partialNumber) + { + $checkDigit = self::checksum($partialNumber * 10); + if ($checkDigit === 0) { + return 0; + } + + return (string) 10 - $checkDigit; + } + + /** + * Checks whether a number (partial number + check digit) is Luhn compliant + * + * @return boolean + */ + public static function isValid($number) + { + return self::checksum($number) === 0; + } +} diff --git a/test/Faker/Util/LuhnTest.php b/test/Faker/Util/LuhnTest.php new file mode 100644 index 0000000000..5725d4ed9d --- /dev/null +++ b/test/Faker/Util/LuhnTest.php @@ -0,0 +1,53 @@ +assertEquals($checkDigit, Luhn::computeCheckDigit($partialNumber)); + } + + public function validatorProvider() + { + return [ + ['79927398710', false], + ['79927398711', false], + ['79927398712', false], + ['79927398713', true], + ['79927398714', false], + ['79927398715', false], + ['79927398716', false], + ['79927398717', false], + ['79927398718', false], + ['79927398719', false] + ]; + } + + /** + * @dataProvider validatorProvider + */ + public function testIsValid($number, $isValid) + { + $this->assertEquals($isValid, Luhn::isValid($number)); + } +} From d768a76d3204d978317a7977899c86ab68277b6a Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 1 Sep 2014 18:03:34 +0200 Subject: [PATCH 2/6] Switch to internal luhn calculator, step 1 --- src/Faker/Provider/Payment.php | 59 ++++++++++--------- src/Faker/Provider/fr_FR/Company.php | 36 ++++------- .../Framework/Constraint/IsValidSiren.php | 18 ------ test/Faker/Provider/PaymentTest.php | 35 ++++++++--- test/Faker/Provider/fr_FR/CompanyTest.php | 20 ++++--- test/Faker/Util/LuhnTest.php | 13 +++- 6 files changed, 94 insertions(+), 87 deletions(-) delete mode 100644 test/Faker/PHPUnit/Framework/Constraint/IsValidSiren.php diff --git a/src/Faker/Provider/Payment.php b/src/Faker/Provider/Payment.php index c54ad1af06..2eb9509aee 100644 --- a/src/Faker/Provider/Payment.php +++ b/src/Faker/Provider/Payment.php @@ -2,6 +2,8 @@ namespace Faker\Provider; +use Faker\Util\Luhn; + class Payment extends Base { public static $expirationDateFormat = "m/y"; @@ -15,38 +17,38 @@ class Payment extends Base // see http://en.wikipedia.org/wiki/Bank_card_number for a reference of existing prefixes protected static $cardParams = array( 'Visa' => array( - "4539#########", - "4539############", - "4556#########", - "4556############", - "4916#########", - "4916############", - "4532#########", - "4532############", - "4929#########", - "4929############", - "40240071#####", - "40240071########", - "4485#########", - "4485############", - "4716#########", - "4716############", - "4############", - "4###############" + "4539########", + "4539###########", + "4556########", + "4556###########", + "4916########", + "4916###########", + "4532########", + "4532###########", + "4929########", + "4929###########", + "40240071####", + "40240071#######", + "4485########", + "4485###########", + "4716########", + "4716###########", + "4###########", + "4##############" ), 'MasterCard' => array( - "51##############", - "52##############", - "53##############", - "54##############", - "55##############" + "51#############", + "52#############", + "53#############", + "54#############", + "55#############" ), 'American Express' => array( - "34#############", - "37#############" + "34############", + "37############" ), 'Discover Card' => array( - "6011############" + "6011###########" ), ); @@ -144,13 +146,14 @@ public static function creditCardNumber($type = null, $formatted = false, $separ $mask = static::randomElement(static::$cardParams[$type]); $number = static::numerify($mask); + $number .= Luhn::computeCheckDigit($number); if ($formatted) { $p1 = substr($number, 0, 4); $p2 = substr($number, 4, 4); $p3 = substr($number, 8, 4); $p4 = substr($number, 12); - $number = $p1 .$separator . $p2 . $separator . $p3 . $separator . $p4; + $number = $p1 . $separator . $p2 . $separator . $p3 . $separator . $p4; } return $number; @@ -197,6 +200,7 @@ public function creditCardDetails($valid = true) /** * International Bank Account Number (IBAN) + * * @link http://en.wikipedia.org/wiki/International_Bank_Account_Number * @param string $countryCode ISO 3166-1 alpha-2 country code * @param string $prefix for generating bank account number of a specific bank @@ -267,6 +271,7 @@ protected static function iban($countryCode, $prefix = '', $length = null) /** * Calculates a checksum for the national bank and branch code part in the IBAN. + * * @param string $iban randomly generated $iban * @param string $countryCode ISO 3166-1 alpha-2 country code * @return string IBAN with one character altered to a proper checksum diff --git a/src/Faker/Provider/fr_FR/Company.php b/src/Faker/Provider/fr_FR/Company.php index f346074720..6d4a86ba0a 100644 --- a/src/Faker/Provider/fr_FR/Company.php +++ b/src/Faker/Provider/fr_FR/Company.php @@ -2,6 +2,8 @@ namespace Faker\Provider\fr_FR; +use Faker\Util\Luhn; + class Company extends \Faker\Provider\Company { /** @@ -102,7 +104,9 @@ public function catchPhrase() /** * Generates a siret number (14 digits) that passes the Luhn check. + * * Use $maxSequentialDigits to make sure the digits at position 2 to 5 are not zeros. + * * @see http://en.wikipedia.org/wiki/Luhn_algorithm * @param int $maxSequentialDigits The maximum number of digits for the sequential number (> 0 && <= 4). * @return string @@ -165,37 +169,19 @@ public static function siret($maxSequentialDigits = 2) /** * Generates a siren number (9 digits) that passes the Luhn check. + * * @see http://en.wikipedia.org/wiki/Luhn_algorithm * @return string */ - public static function siren() + public function siren($formatted = true) { - $siren = ''; - $sum = 0; - for ($i = 9; $i > 1; $i--) { - - $digit = mt_rand(0, 9); - $isEven = $i % 2 === 0; - - $tmp = $isEven ? $digit * 2 : $digit; - if ($tmp >= 10) { - $tmp -= 9; - } - $sum += $tmp; - - $siren = $digit . $siren; - - } - - $mod = $sum % 10; - if ($mod === 0) { - $siren = '0' . $siren; - } else { - $siren = (10 - $mod) . $siren; + $siren = $this->numerify('########'); + $siren .= Luhn::computeCheckDigit($siren); + if ($formatted) { + $siren = substr($siren, 0, 3) . ' ' . substr($siren, 3, 3) . ' ' . substr($siren, 6, 3); } - return preg_replace("/([0-9]{3})([0-9]{3})([0-9]{3})/", "$1 $2 $3", $siren); - + return $siren; } /** diff --git a/test/Faker/PHPUnit/Framework/Constraint/IsValidSiren.php b/test/Faker/PHPUnit/Framework/Constraint/IsValidSiren.php deleted file mode 100644 index d1502d01b8..0000000000 --- a/test/Faker/PHPUnit/Framework/Constraint/IsValidSiren.php +++ /dev/null @@ -1,18 +0,0 @@ -addProvider(new \Faker\Provider\Base($faker)); - $faker->addProvider(new \Faker\Provider\DateTime($faker)); - $faker->addProvider(new \Faker\Provider\Person($faker)); - $faker->addProvider(new \Faker\Provider\Payment($faker)); + $faker = new Generator(); + $faker->addProvider(new BaseProvider($faker)); + $faker->addProvider(new DateTimeProvider($faker)); + $faker->addProvider(new PersonProvider($faker)); + $faker->addProvider(new PaymentProvider($faker)); $this->faker = $faker; } @@ -19,9 +26,23 @@ public function testCreditCardTypeReturnsValidVendorName() $this->assertTrue(in_array($this->faker->creditCardType, array('Visa', 'MasterCard', 'American Express', 'Discover Card'))); } - public function testCreditCardNumberReturnsValidCreditCardNumber() + public function creditCardNumberProvider() + { + return [ + ['Discover Card', '/^6011\d{12}$/'], + ['Visa', '/^4\d{12,15}$/'], + ['MasterCard', '/^5[1-5]\d{14}$/'] + ]; + } + + /** + * @dataProvider creditCardNumberProvider + */ + public function testCreditCardNumberReturnsValidCreditCardNumber($type, $regexp) { - $this->assertRegExp('/^6011\d{12}$/', $this->faker->creditCardNumber('Discover Card')); + $cardNumber = $this->faker->creditCardNumber($type); + $this->assertRegExp($regexp, $cardNumber); + $this->assertTrue(Luhn::isValid($cardNumber)); } public function testCreditCardNumberCanFormatOutput() diff --git a/test/Faker/Provider/fr_FR/CompanyTest.php b/test/Faker/Provider/fr_FR/CompanyTest.php index de8d8ab62a..4985ac38ba 100644 --- a/test/Faker/Provider/fr_FR/CompanyTest.php +++ b/test/Faker/Provider/fr_FR/CompanyTest.php @@ -2,8 +2,10 @@ namespace Faker\Test\Provider\fr_FR; +use Faker\Generator; use Faker\Provider\fr_FR\Company; use Faker\PHPUnit\Framework\Constraint as Constraint; +use Faker\Util\Luhn; class CompanyTest extends \PHPUnit_Framework_TestCase { @@ -12,11 +14,6 @@ private static function isValidSiret() return new Constraint\IsValidSiret(); } - private static function isValidSiren() - { - return new Constraint\IsValidSiren(); - } - public function testParagraphWithNegativeNbDigitsReturnsAWellFormattedSiret() { $siret = Company::siret(-1); @@ -52,10 +49,17 @@ public function testParagraphWithValidNbDigitsReturnsAWellFormattedSiret() public function testSirenReturnsAValidAndWellFormattedSiren() { - $siret = Company::siren(); + $faker = new Generator(); + $faker->addProvider(new Company($faker)); + + $siren = $faker->siren(); + $this->assertRegExp("/^\d{3} \d{3} \d{3}$/", $siren); + $siren = str_replace(' ', '', $siren); + $this->assertTrue(Luhn::isValid($siren)); - $this->assertThat($siret, self :: isValidSiren()); - $this->assertRegExp("/[\d]{3} [\d]{3} [\d]{3}/", $siret); + $siren = $faker->siren(false); + $this->assertRegExp("/^\d{9}$/", $siren); + $this->assertTrue(Luhn::isValid($siren)); } public function testCatchPhraseValidationReturnsFalse() diff --git a/test/Faker/Util/LuhnTest.php b/test/Faker/Util/LuhnTest.php index 5725d4ed9d..55e87746b1 100644 --- a/test/Faker/Util/LuhnTest.php +++ b/test/Faker/Util/LuhnTest.php @@ -15,7 +15,13 @@ public function checkDigitProvider() ['37144963539843', '1'], ['561059108101825', '0'], ['601100099013942', '4'], - ['510510510510510', '0'] + ['510510510510510', '0'], + [7992739871, '3'], + [3852000002323, '7'], + [37144963539843, '1'], + [561059108101825, '0'], + [601100099013942, '4'], + [510510510510510, '0'] ]; } @@ -24,6 +30,7 @@ public function checkDigitProvider() */ public function testComputeCheckDigit($partialNumber, $checkDigit) { + $this->assertInternalType('string', $checkDigit); $this->assertEquals($checkDigit, Luhn::computeCheckDigit($partialNumber)); } @@ -39,7 +46,9 @@ public function validatorProvider() ['79927398716', false], ['79927398717', false], ['79927398718', false], - ['79927398719', false] + ['79927398719', false], + [79927398713, true], + [79927398714, false], ]; } From 17a2033b95dba22686c48a95d0d01c8daab632c5 Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Mon, 1 Sep 2014 18:31:14 +0200 Subject: [PATCH 3/6] Use new Luhn generator in French siren/siret formatter --- readme.md | 4 -- src/Faker/Provider/fr_FR/Company.php | 67 +++---------------- src/Faker/Util/Luhn.php | 2 +- .../Constraint/IsValidSirenSiret.php | 39 ----------- .../Framework/Constraint/IsValidSiret.php | 18 ----- test/Faker/Provider/PaymentTest.php | 2 + test/Faker/Provider/fr_FR/CompanyTest.php | 67 ++++++++----------- 7 files changed, 41 insertions(+), 158 deletions(-) delete mode 100644 test/Faker/PHPUnit/Framework/Constraint/IsValidSirenSiret.php delete mode 100644 test/Faker/PHPUnit/Framework/Constraint/IsValidSiret.php diff --git a/readme.md b/readme.md index dd956314c7..751c5c485d 100644 --- a/readme.md +++ b/readme.md @@ -719,10 +719,6 @@ echo $faker->siren; // 082 250 104 // Generates a random SIRET number echo $faker->siret; // 347 355 708 00224 - -// Generates a random SIRET number (controlling the number of sequential digits) -echo $faker->siret(3); // 438 472 611 01513 - ``` ### `Faker\Provider\fr_FR\Address` diff --git a/src/Faker/Provider/fr_FR/Company.php b/src/Faker/Provider/fr_FR/Company.php index 6d4a86ba0a..fc03e2e758 100644 --- a/src/Faker/Provider/fr_FR/Company.php +++ b/src/Faker/Provider/fr_FR/Company.php @@ -105,77 +105,30 @@ public function catchPhrase() /** * Generates a siret number (14 digits) that passes the Luhn check. * - * Use $maxSequentialDigits to make sure the digits at position 2 to 5 are not zeros. - * - * @see http://en.wikipedia.org/wiki/Luhn_algorithm - * @param int $maxSequentialDigits The maximum number of digits for the sequential number (> 0 && <= 4). + * @see http://fr.wikipedia.org/wiki/Syst%C3%A8me_d'identification_du_r%C3%A9pertoire_des_%C3%A9tablissements * @return string */ - public static function siret($maxSequentialDigits = 2) + public function siret($formatted = true) { - - if ($maxSequentialDigits > 4 || $maxSequentialDigits <= 0) { - $maxSequentialDigits = 2; - } - - $controlDigit = mt_rand(0, 9); - $siret = $sum = $controlDigit; - - $position = 2; - for ($i = 0; $i < $maxSequentialDigits; $i++) { - - $sequentialDigit = mt_rand(0, 9); - $isEven = $position++ % 2 === 0; - - $tmp = $isEven ? $sequentialDigit * 2 : $sequentialDigit; - if ($tmp >= 10) { - $tmp -= 9; - } - $sum += $tmp; - - $siret = $sequentialDigit . $siret; - - } - - $siret = str_pad($siret, 5, '0', STR_PAD_LEFT); - - $position = 6; - for ($i = 0; $i < 7; $i++) { - - $digit = mt_rand(0, 9); - $isEven = $position++ % 2 === 0; - - $tmp = $isEven ? $digit * 2 : $digit; - if ($tmp >= 10) { - $tmp -= 9; - } - $sum += $tmp; - - $siret = $digit . $siret; - - } - - $mod = $sum % 10; - if ($mod === 0) { - $siret = '00' . $siret; - } else { - // Use the odd position to avoid multiplying by two - $siret = '0' . (10 - $mod) . $siret; + $siret = $this->siren(false); + $siret .= $this->numerify('####'); + $siret .= Luhn::computeCheckDigit($siret); + if ($formatted) { + $siret = substr($siret, 0, 3) . ' ' . substr($siret, 3, 3) . ' ' . substr($siret, 6, 3) . ' ' . substr($siret, 9, 5); } - return preg_replace("/([0-9]{3})([0-9]{3})([0-9]{3})([0-9]{5})/", "$1 $2 $3 $4", $siret); - + return $siret; } /** * Generates a siren number (9 digits) that passes the Luhn check. * - * @see http://en.wikipedia.org/wiki/Luhn_algorithm + * @see http://fr.wikipedia.org/wiki/Syst%C3%A8me_d%27identification_du_r%C3%A9pertoire_des_entreprises * @return string */ public function siren($formatted = true) { - $siren = $this->numerify('########'); + $siren = $this->numerify('%#######'); $siren .= Luhn::computeCheckDigit($siren); if ($formatted) { $siren = substr($siren, 0, 3) . ' ' . substr($siren, 3, 3) . ' ' . substr($siren, 6, 3); diff --git a/src/Faker/Util/Luhn.php b/src/Faker/Util/Luhn.php index 51a92a5818..1b9139e2aa 100644 --- a/src/Faker/Util/Luhn.php +++ b/src/Faker/Util/Luhn.php @@ -15,7 +15,7 @@ class Luhn /** * @return int */ - private static function checksum($number) + private static function checksum($number) { $sum = 0; foreach (str_split(strrev((string) $number)) as $i => $digit) { diff --git a/test/Faker/PHPUnit/Framework/Constraint/IsValidSirenSiret.php b/test/Faker/PHPUnit/Framework/Constraint/IsValidSirenSiret.php deleted file mode 100644 index 2b2f23e0ec..0000000000 --- a/test/Faker/PHPUnit/Framework/Constraint/IsValidSirenSiret.php +++ /dev/null @@ -1,39 +0,0 @@ -getLength()) { - return false; - } - - $sum = 0; - // IMPORTANT : from right to left - $position = 1; - for ($i = strlen($code) - 1; $i >= 0; $i--) { - $isEven = (($position++ % 2) === 0); - $tmp = $isEven ? $code[$i] * 2 : $code[$i]; - if ($tmp >= 10) $tmp -= 9; - $sum += $tmp; - } - - return ($sum % 10 === 0); - - } - - public function toString() - { - return sprintf('is a valid %s number', $this->getName()); - } - - abstract protected function getLength(); - - abstract protected function getName(); - -} diff --git a/test/Faker/PHPUnit/Framework/Constraint/IsValidSiret.php b/test/Faker/PHPUnit/Framework/Constraint/IsValidSiret.php deleted file mode 100644 index 4725fbec90..0000000000 --- a/test/Faker/PHPUnit/Framework/Constraint/IsValidSiret.php +++ /dev/null @@ -1,18 +0,0 @@ -addProvider(new Company($faker)); + $this->faker = $faker; } - public function testParagraphWithNegativeNbDigitsReturnsAWellFormattedSiret() + public function testSiretReturnsAValidSiret() { - $siret = Company::siret(-1); - - $this->assertThat($siret, self::isValidSiret()); - $this->assertRegExp("/[\d]{3} [\d]{3} [\d]{3} 00[\d]{3}/", $siret); + $siret = $this->faker->siret(false); + $this->assertRegExp("/^\d{14}$/", $siret); + $this->assertTrue(Luhn::isValid($siret)); } - public function testParagraphWithInvalidNbDigitsReturnsAWellFormattedSiret() + public function testSiretReturnsAWellFormattedSiret() { - $siret = Company::siret(6); - - $this->assertThat($siret, self::isValidSiret()); - $this->assertRegExp("/[\d]{3} [\d]{3} [\d]{3} 00[\d]{3}/", $siret); + $siret = $this->faker->siret(); + $this->assertRegExp("/^\d{3}\s\d{3}\s\d{3}\s\d{5}$/", $siret); + $siret = str_replace(' ', '', $siret); + $this->assertTrue(Luhn::isValid($siret)); } - public function testParagraphWithValidNbDigitsReturnsAWellFormattedSiret() + public function testSirenReturnsAValidSiren() { - $siret1 = Company::siret(1); - $siret2 = Company::siret(2); - $siret3 = Company::siret(3); - $siret4 = Company::siret(4); - - $this->assertThat($siret1, self :: isValidSiret()); - $this->assertRegExp("/[\d]{3} [\d]{3} [\d]{3} 000[\d]{2}/", $siret1); - $this->assertThat($siret2, self :: isValidSiret()); - $this->assertRegExp("/[\d]{3} [\d]{3} [\d]{3} 00[\d]{3}/", $siret2); - $this->assertThat($siret3, self :: isValidSiret()); - $this->assertRegExp("/[\d]{3} [\d]{3} [\d]{3} 0[\d]{4}/", $siret3); - $this->assertThat($siret4, self :: isValidSiret()); - $this->assertRegExp("/[\d]{3} [\d]{3} [\d]{3} [\d]{5}/", $siret4); + $siren = $this->faker->siren(false); + $this->assertRegExp("/^\d{9}$/", $siren); + $this->assertTrue(Luhn::isValid($siren)); } - public function testSirenReturnsAValidAndWellFormattedSiren() + public function testSirenReturnsAWellFormattedSiren() { - $faker = new Generator(); - $faker->addProvider(new Company($faker)); - - $siren = $faker->siren(); - $this->assertRegExp("/^\d{3} \d{3} \d{3}$/", $siren); + $siren = $this->faker->siren(); + $this->assertRegExp("/^\d{3}\s\d{3}\s\d{3}$/", $siren); $siren = str_replace(' ', '', $siren); $this->assertTrue(Luhn::isValid($siren)); + } - $siren = $faker->siren(false); - $this->assertRegExp("/^\d{9}$/", $siren); - $this->assertTrue(Luhn::isValid($siren)); + public function testCatchPhraseReturnsValidCatchPhrase() + { + $this->assertTrue(TestableCompany::isCatchPhraseValid($this->faker->catchPhrase())); } - public function testCatchPhraseValidationReturnsFalse() + public function testIsCatchPhraseValidReturnsFalseWhenAWordsAppearsTwice() { $isCatchPhraseValid = TestableCompany::isCatchPhraseValid('La sécurité de rouler en toute sécurité'); - $this->assertFalse($isCatchPhraseValid); } - public function testCatchPhraseValidationReturnsTrue() + public function testIsCatchPhraseValidReturnsTrueWhenNoWordAppearsTwice() { $isCatchPhraseValid = TestableCompany::isCatchPhraseValid('La sécurité de rouler en toute simplicité'); - $this->assertTrue($isCatchPhraseValid); } } From b25d30cda9be622637834299a36702dfe69e3a0d Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Tue, 2 Sep 2014 22:26:33 +0200 Subject: [PATCH 4/6] Migrate last tests to Luhn util --- src/Faker/Provider/sv_SE/Person.php | 37 ++------------- src/Faker/Util/Luhn.php | 19 ++++---- test/Faker/Provider/PaymentTest.php | 10 ++-- test/Faker/Provider/fr_FR/CompanyTest.php | 1 - test/Faker/Provider/sv_SE/PersonTest.php | 37 ++++++--------- test/Faker/Util/LuhnTest.php | 56 +++++++++++------------ 6 files changed, 60 insertions(+), 100 deletions(-) diff --git a/src/Faker/Provider/sv_SE/Person.php b/src/Faker/Provider/sv_SE/Person.php index 57051f231a..a7b2fe6876 100644 --- a/src/Faker/Provider/sv_SE/Person.php +++ b/src/Faker/Provider/sv_SE/Person.php @@ -2,6 +2,8 @@ namespace Faker\Provider\sv_SE; +use Faker\Util\Luhn; + class Person extends \Faker\Provider\Person { protected static $formats = array( @@ -134,41 +136,8 @@ public function personalIdentityNumber(\DateTime $birthdate = null, $gender = nu } - $checksum = $this->calculateChecksum($datePart . $randomDigits); + $checksum = Luhn::computeCheckDigit($datePart . $randomDigits); return $datePart . '-' . $randomDigits . $checksum; } - - /** - * Luhn algorithm - * - * This is a naive implementation that only works - * specifically for numbered strings of size 9 - * - * @link http://en.wikipedia.org/wiki/Luhn_algorithm - * @param string $input - * @return int checksum - */ - protected function calculateChecksum($input) - { - $multiplied = implode( - array_map( - function ($first, $second) { - return $first * $second; - }, - str_split($input), - array(2, 1, 2, 1, 2, 1, 2, 1, 2) - ) - ); - - $summed = (int)array_reduce( - str_split($multiplied), - function ($carry, $item) { - return (int)($carry + $item); - } - ); - - $checksum = 10 - ($summed%10); - return $checksum == 10 ? 0 : $checksum; - } } diff --git a/src/Faker/Util/Luhn.php b/src/Faker/Util/Luhn.php index 1b9139e2aa..8d9fe049ee 100644 --- a/src/Faker/Util/Luhn.php +++ b/src/Faker/Util/Luhn.php @@ -17,15 +17,14 @@ class Luhn */ private static function checksum($number) { + $number = (string) $number; + $length = strlen($number); $sum = 0; - foreach (str_split(strrev((string) $number)) as $i => $digit) { - if ($i % 2 !== 0) { - $digit = $digit * 2; - if ($digit > 9) { - $digit = (int) ($digit / 10) + $digit % 10; - } - } - $sum += $digit; + for ($i = $length - 1; $i >= 0; $i -= 2) { + $sum += $number{$i}; + } + for ($i = $length - 2; $i >= 0; $i -= 2) { + $sum += array_sum(str_split($number{$i} * 2)); } return $sum % 10; @@ -36,12 +35,12 @@ private static function checksum($number) */ public static function computeCheckDigit($partialNumber) { - $checkDigit = self::checksum($partialNumber * 10); + $checkDigit = self::checksum($partialNumber . '0'); if ($checkDigit === 0) { return 0; } - return (string) 10 - $checkDigit; + return (string) (10 - $checkDigit); } /** diff --git a/test/Faker/Provider/PaymentTest.php b/test/Faker/Provider/PaymentTest.php index 8739bb8096..84578f21b6 100644 --- a/test/Faker/Provider/PaymentTest.php +++ b/test/Faker/Provider/PaymentTest.php @@ -30,11 +30,11 @@ public function testCreditCardTypeReturnsValidVendorName() public function creditCardNumberProvider() { - return [ - ['Discover Card', '/^6011\d{12}$/'], - ['Visa', '/^4\d{12,15}$/'], - ['MasterCard', '/^5[1-5]\d{14}$/'] - ]; + return array( + array('Discover Card', '/^6011\d{12}$/'), + array('Visa', '/^4\d{12,15}$/'), + array('MasterCard', '/^5[1-5]\d{14}$/') + ); } /** diff --git a/test/Faker/Provider/fr_FR/CompanyTest.php b/test/Faker/Provider/fr_FR/CompanyTest.php index afcbf74f0a..336a915b3c 100644 --- a/test/Faker/Provider/fr_FR/CompanyTest.php +++ b/test/Faker/Provider/fr_FR/CompanyTest.php @@ -4,7 +4,6 @@ use Faker\Generator; use Faker\Provider\fr_FR\Company; -use Faker\PHPUnit\Framework\Constraint as Constraint; use Faker\Util\Luhn; class CompanyTest extends \PHPUnit_Framework_TestCase diff --git a/test/Faker/Provider/sv_SE/PersonTest.php b/test/Faker/Provider/sv_SE/PersonTest.php index 6f56f52a2c..91cece1aa9 100644 --- a/test/Faker/Provider/sv_SE/PersonTest.php +++ b/test/Faker/Provider/sv_SE/PersonTest.php @@ -4,6 +4,7 @@ use Faker\Generator; use Faker\Provider\sv_SE\Person; +use Faker\Util\Luhn; class PersonTest extends \PHPUnit_Framework_TestCase { @@ -31,37 +32,29 @@ public function provideSeedAndExpectedReturn() /** * @dataProvider provideSeedAndExpectedReturn */ - public function testPersonalIdentityNumber($seed, $birthdate, $expected) + public function testPersonalIdentityNumberUsesBirthDateIfProvided($seed, $birthdate, $expected) { $faker = $this->faker; $faker->seed($seed); - - $this->assertEquals( - $expected, - $faker->personalIdentityNumber(\DateTime::createFromFormat('ymd', $birthdate)) - ); + $pin = $faker->personalIdentityNumber(\DateTime::createFromFormat('ymd', $birthdate)); + $this->assertEquals($expected, $pin); } - public function testUsesOddValuesForMales() + public function testPersonalIdentityNumberGeneratesLuhnCompliantNumbers() { - $faker = $this->faker; - $faker->seed(1); - - $this->assertEquals( - '720727-5715', - $faker->personalIdentityNumber(\DateTime::createFromFormat('ymd', '720727'), 'male') - ); + $pin = str_replace('-', '', $this->faker->personalIdentityNumber()); + $this->assertTrue(Luhn::isValid($pin)); } - public function testUsesEvenValuesForFemales() + public function testPersonalIdentityNumberGeneratesEvenValuesForMales() { - $faker = $this->faker; - $faker->seed(1); - - $this->assertEquals( - '720727-5707', - $faker->personalIdentityNumber(\DateTime::createFromFormat('ymd', '720727'), 'female') - ); + $pin = $this->faker->personalIdentityNumber(null, 'male'); + $this->assertEquals(1, $pin{9} % 2); + } + public function testPersonalIdentityNumberGeneratesOddValuesForFemales() + { + $pin = $this->faker->personalIdentityNumber(null, 'female'); + $this->assertEquals(0, $pin{9} % 2); } } diff --git a/test/Faker/Util/LuhnTest.php b/test/Faker/Util/LuhnTest.php index 55e87746b1..24b83490c3 100644 --- a/test/Faker/Util/LuhnTest.php +++ b/test/Faker/Util/LuhnTest.php @@ -9,20 +9,20 @@ class LuhnTest extends \PHPUnit_Framework_TestCase public function checkDigitProvider() { - return [ - ['7992739871', '3'], - ['3852000002323', '7'], - ['37144963539843', '1'], - ['561059108101825', '0'], - ['601100099013942', '4'], - ['510510510510510', '0'], - [7992739871, '3'], - [3852000002323, '7'], - [37144963539843, '1'], - [561059108101825, '0'], - [601100099013942, '4'], - [510510510510510, '0'] - ]; + return array( + array('7992739871', '3'), + array('3852000002323', '7'), + array('37144963539843', '1'), + array('561059108101825', '0'), + array('601100099013942', '4'), + array('510510510510510', '0'), + array(7992739871, '3'), + array(3852000002323, '7'), + array(37144963539843, '1'), + array(561059108101825, '0'), + array(601100099013942, '4'), + array(510510510510510, '0') + ); } /** @@ -36,20 +36,20 @@ public function testComputeCheckDigit($partialNumber, $checkDigit) public function validatorProvider() { - return [ - ['79927398710', false], - ['79927398711', false], - ['79927398712', false], - ['79927398713', true], - ['79927398714', false], - ['79927398715', false], - ['79927398716', false], - ['79927398717', false], - ['79927398718', false], - ['79927398719', false], - [79927398713, true], - [79927398714, false], - ]; + return array( + array('79927398710', false), + array('79927398711', false), + array('79927398712', false), + array('79927398713', true), + array('79927398714', false), + array('79927398715', false), + array('79927398716', false), + array('79927398717', false), + array('79927398718', false), + array('79927398719', false), + array(79927398713, true), + array(79927398714, false), + ); } /** From ad0d506c0800cea14ce76add6414e8626abc3b2b Mon Sep 17 00:00:00 2001 From: Francois Zaninotto Date: Tue, 2 Sep 2014 22:41:33 +0200 Subject: [PATCH 5/6] Move Luhn calculator to a Calculator namespace --- src/Faker/{Util => Calculator}/Luhn.php | 2 +- src/Faker/Provider/Payment.php | 2 +- src/Faker/Provider/fr_FR/Company.php | 2 +- src/Faker/Provider/sv_SE/Person.php | 2 +- test/Faker/{Util => Calculator}/LuhnTest.php | 4 ++-- test/Faker/Provider/PaymentTest.php | 2 +- test/Faker/Provider/fr_FR/CompanyTest.php | 2 +- test/Faker/Provider/sv_SE/PersonTest.php | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) rename src/Faker/{Util => Calculator}/Luhn.php (97%) rename test/Faker/{Util => Calculator}/LuhnTest.php (96%) diff --git a/src/Faker/Util/Luhn.php b/src/Faker/Calculator/Luhn.php similarity index 97% rename from src/Faker/Util/Luhn.php rename to src/Faker/Calculator/Luhn.php index 8d9fe049ee..4caad29fb3 100644 --- a/src/Faker/Util/Luhn.php +++ b/src/Faker/Calculator/Luhn.php @@ -1,6 +1,6 @@ Date: Tue, 2 Sep 2014 22:50:07 +0200 Subject: [PATCH 6/6] MAke French siret formatter more realistic --- src/Faker/Provider/fr_FR/Company.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Faker/Provider/fr_FR/Company.php b/src/Faker/Provider/fr_FR/Company.php index 2fc0695a1b..5f440666f6 100644 --- a/src/Faker/Provider/fr_FR/Company.php +++ b/src/Faker/Provider/fr_FR/Company.php @@ -53,6 +53,8 @@ class Company extends \Faker\Provider\Company */ protected static $companySuffix = array('SA', 'S.A.', 'SARL', 'S.A.R.L.', 'S.A.S.', 'et Fils'); + protected static $siretNicFormats = array('####', '0###', '00#%'); + /** * Returns a random catch phrase noun. * @@ -111,7 +113,8 @@ public function catchPhrase() public function siret($formatted = true) { $siret = $this->siren(false); - $siret .= $this->numerify('####'); + $nicFormat = static::randomElement(static::$siretNicFormats); + $siret .= $this->numerify($nicFormat); $siret .= Luhn::computeCheckDigit($siret); if ($formatted) { $siret = substr($siret, 0, 3) . ' ' . substr($siret, 3, 3) . ' ' . substr($siret, 6, 3) . ' ' . substr($siret, 9, 5);