Skip to content
This repository has been archived by the owner on Dec 11, 2020. It is now read-only.

Refactor formatters with Luhn validation, and add a dedicated calculator service #414

Merged
merged 6 commits into from
Sep 2, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
55 changes: 55 additions & 0 deletions src/Faker/Calculator/Luhn.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace Faker\Calculator;

/**
* Utility class for generating Luhn checksum and validating a number
*
* Luhn algorithm is used to validate credit card numbers, IMEI numbers, and
* National Provider Identifier numbers.
*
* @see http://en.wikipedia.org/wiki/Luhn_algorithm
*/
class Luhn
{
/**
* @return int
*/
private static function checksum($number)
{
$number = (string) $number;
$length = strlen($number);
$sum = 0;
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;
}

/**
* @return string
*/
public static function computeCheckDigit($partialNumber)
{
$checkDigit = self::checksum($partialNumber . '0');
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;
}
}
59 changes: 32 additions & 27 deletions src/Faker/Provider/Payment.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Faker\Provider;

use Faker\Calculator\Luhn;

class Payment extends Base
{
public static $expirationDateFormat = "m/y";
Expand All @@ -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###########"
),
);

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
102 changes: 22 additions & 80 deletions src/Faker/Provider/fr_FR/Company.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Faker\Provider\fr_FR;

use Faker\Calculator\Luhn;

class Company extends \Faker\Provider\Company
{
/**
Expand Down Expand Up @@ -51,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.
*
Expand Down Expand Up @@ -102,100 +106,38 @@ 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;

$siret = $this->siren(false);
$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);
}

$mod = $sum % 10;
if ($mod === 0) {
$siret = '00' . $siret;
} else {
// Use the odd position to avoid multiplying by two
$siret = '0' . (10 - $mod) . $siret;
}

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 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;
}

/**
Expand Down
37 changes: 3 additions & 34 deletions src/Faker/Provider/sv_SE/Person.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Faker\Provider\sv_SE;

use Faker\Calculator\Luhn;

class Person extends \Faker\Provider\Person
{
protected static $formats = array(
Expand Down Expand Up @@ -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;
}
}
Loading