Create "Iban" rule

The validation procedure is adhering to what is described in Wikipedia.

Link: https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
Co-authored-by: Henrique Moody <henriquemoody@gmail.com>
This commit is contained in:
Mazen Touati 2019-02-04 21:24:26 +01:00 committed by Henrique Moody
parent 3723a124c8
commit 6d3eb6afed
No known key found for this signature in database
GPG key ID: 221E9281655813A6
10 changed files with 333 additions and 0 deletions

View file

@ -179,6 +179,7 @@
## Banking
- [CreditCard](rules/CreditCard.md)
- [Iban](rules/Iban.md)
## Other
@ -273,6 +274,7 @@
- [Graph](rules/Graph.md)
- [GreaterThan](rules/GreaterThan.md)
- [HexRgbColor](rules/HexRgbColor.md)
- [Iban](rules/Iban.md)
- [INSTALL](installation.md)
- [Identical](rules/Identical.md)
- [IdentityCard](rules/IdentityCard.md)

View file

@ -45,6 +45,7 @@ Version | Description
See also:
- [Digit](Digit.md)
- [Iban](Iban.md)
- [Luhn](Luhn.md)
- [NoWhitespace](NoWhitespace.md)
- [Regex](Regex.md)

30
docs/rules/Iban.md Normal file
View file

@ -0,0 +1,30 @@
# Iban
- `v::iban()`
Validates whether the input is a valid [IBAN][] (International Bank Account
Number) or not.
```php
v::iban()->validate('SE35 5000 0000 0549 1000 0003'); // true
v::iban()->validate('ch9300762011623852957'); // true
v::iban()->validate('ZZ32 5000 5880 7742'); // false
v::iban()->validate(123456789); // false
v::iban()->validate(''); // false
```
## Changelog
Version | Description
--------|-------------
2.0.0 | Created
***
See also:
- [CreditCard](CreditCard.md)
- [MacAddress](MacAddress.md)
- [PostalCode](PostalCode.md)
[IBAN]: https://en.wikipedia.org/wiki/International_Bank_Account_Number

View file

@ -19,5 +19,6 @@ Version | Description
See also:
- [Domain](Domain.md)
- [Iban](Iban.md)
- [Ip](Ip.md)
- [Tld](Tld.md)

View file

@ -26,3 +26,4 @@ Version | Description
See also:
- [CountryCode](CountryCode.md)
- [Iban](Iban.md)

View file

@ -0,0 +1,32 @@
<?php
/*
* This file is part of Respect/Validation.
*
* (c) Alexandre Gomes Gaigalas <alexandre@gaigalas.net>
*
* For the full copyright and license information, please view the "LICENSE.md"
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Respect\Validation\Exceptions;
/**
* @author Mazen Touati <mazen_touati@hotmail.com>
*/
final class IbanException extends ValidationException
{
/**
* {@inheritdoc}
*/
public static $defaultTemplates = [
self::MODE_DEFAULT => [
self::STANDARD => '{{name}} must be a valid IBAN',
],
self::MODE_NEGATIVE => [
self::STANDARD => '{{name}} must not be a valid IBAN',
],
];
}

152
library/Rules/Iban.php Normal file
View file

@ -0,0 +1,152 @@
<?php
/*
* This file is part of Respect/Validation.
*
* (c) Alexandre Gomes Gaigalas <alexandre@gaigalas.net>
*
* For the full copyright and license information, please view the "LICENSE.md"
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Respect\Validation\Rules;
use function bcmod;
use function is_string;
use function ord;
use function preg_match;
use function preg_replace_callback;
use function str_replace;
use function strlen;
use function substr;
/**
* Validates whether the input is a valid IBAN (International Bank Account Number) or not.
*
* @author Mazen Touati <mazen_touati@hotmail.com>
*/
final class Iban extends AbstractRule
{
private const COUNTRIES_LENGTHS = [
'AL' => 28,
'AD' => 24,
'AT' => 20,
'AZ' => 28,
'BH' => 22,
'BE' => 16,
'BA' => 20,
'BR' => 29,
'BG' => 22,
'CR' => 21,
'HR' => 21,
'CY' => 28,
'CZ' => 24,
'DK' => 18,
'DO' => 28,
'EE' => 20,
'FO' => 18,
'FI' => 18,
'FR' => 27,
'GE' => 22,
'DE' => 22,
'GI' => 23,
'GR' => 27,
'GL' => 18,
'GT' => 28,
'HU' => 28,
'IS' => 26,
'IE' => 22,
'IL' => 23,
'IT' => 27,
'JO' => 30,
'KZ' => 20,
'KW' => 30,
'LV' => 21,
'LB' => 28,
'LI' => 21,
'LT' => 20,
'LU' => 20,
'MK' => 19,
'MT' => 31,
'MR' => 27,
'MU' => 30,
'MD' => 24,
'MC' => 27,
'ME' => 22,
'NL' => 18,
'NO' => 15,
'PK' => 24,
'PL' => 28,
'PS' => 29,
'PT' => 25,
'QA' => 29,
'XK' => 20,
'RO' => 24,
'LC' => 32,
'SM' => 27,
'ST' => 25,
'SA' => 24,
'RS' => 22,
'SC' => 31,
'SK' => 24,
'SI' => 19,
'ES' => 24,
'SE' => 24,
'CH' => 21,
'TL' => 23,
'TN' => 24,
'TR' => 26,
'UA' => 29,
'AE' => 23,
'GB' => 22,
'VG' => 24,
];
/**
* {@inheritdoc}
*/
public function validate($input): bool
{
if (!is_string($input)) {
return false;
}
$iban = str_replace(' ', '', $input);
if (!preg_match('/[A-Z0-9]{15,34}/', $iban)) {
return false;
}
$countryCode = substr($iban, 0, 2);
if (!$this->hasValidCountryLength($iban, $countryCode)) {
return false;
}
$checkDigits = substr($iban, 2, 2);
$bban = substr($iban, 4);
$rearranged = $bban.$countryCode.$checkDigits;
return bcmod($this->convertToInteger($rearranged), '97') === '1';
}
private function hasValidCountryLength(string $iban, string $countryCode): bool
{
if (!isset(self::COUNTRIES_LENGTHS[$countryCode])) {
return false;
}
return strlen($iban) === self::COUNTRIES_LENGTHS[$countryCode];
}
private function convertToInteger(string $reArrangedIban): string
{
return preg_replace_callback(
'/[A-Z]/',
static function (array $match): int {
return ord($match[0]) - 55;
},
$reArrangedIban
);
}
}

View file

@ -78,6 +78,7 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
* @method static Validator graph(string ...$additionalChars)
* @method static Validator greaterThan($compareTo)
* @method static Validator hexRgbColor()
* @method static Validator iban()
* @method static Validator identical($value)
* @method static Validator identityCard(string $countryCode)
* @method static Validator image(finfo $fileInfo = null)

View file

@ -0,0 +1,42 @@
--CREDITS--
Mazen Touati <mazen_touati@hotmail.com>
--FILE--
<?php
declare(strict_types=1);
require 'vendor/autoload.php';
use Respect\Validation\Exceptions\IbanException;
use Respect\Validation\Exceptions\NestedValidationException;
use Respect\Validation\Validator as v;
try {
v::iban()->check('SE35 5000 5880 7742');
} catch (IbanException $exception) {
echo $exception->getMessage().PHP_EOL;
}
try {
v::not(v::iban())->check('GB82 WEST 1234 5698 7654 32');
} catch (IbanException $exception) {
echo $exception->getMessage().PHP_EOL;
}
try {
v::iban()->assert('NOT AN IBAN');
} catch (NestedValidationException $exception) {
echo $exception->getFullMessage().PHP_EOL;
}
try {
v::not(v::iban())->assert('HU93 1160 0006 0000 0000 1234 5676');
} catch (NestedValidationException $exception) {
echo $exception->getFullMessage().PHP_EOL;
}
?>
--EXPECT--
"SE35 5000 5880 7742" must be a valid IBAN
"GB82 WEST 1234 5698 7654 32" must not be a valid IBAN
- "NOT AN IBAN" must be a valid IBAN
- "HU93 1160 0006 0000 0000 1234 5676" must not be a valid IBAN

View file

@ -0,0 +1,71 @@
<?php
/*
* This file is part of Respect/Validation.
*
* (c) Alexandre Gomes Gaigalas <alexandre@gaigalas.net>
*
* For the full copyright and license information, please view the "LICENSE.md"
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Respect\Validation\Rules;
use Respect\Validation\Test\RuleTestCase;
use stdClass;
/**
* @group rule
*
* @covers \Respect\Validation\Rules\Iban
*
* @author Mazen Touati <mazen_touati@hotmail.com>
*/
final class IbanTest extends RuleTestCase
{
/**
* {@inheritdoc}
*/
public function providerForValidInput(): array
{
$sut = new Iban();
return [
'Belgium' => [$sut, 'BE71 0961 2345 6769'],
'France' => [$sut, 'FR76 3000 6000 0112 3456 7890 189'],
'Germany' => [$sut, 'DE89 3704 0044 0532 0130 00'],
'Greece' => [$sut, 'GR96 0810 0010 0000 0123 4567 890'],
'Romania' => [$sut, 'RO09 BCYP 0000 0012 3456 7890'],
'Saudi Arabia' => [$sut, 'SA44 2000 0001 2345 6789 1234'],
'Spain' => [$sut, 'ES79 2100 0813 6101 2345 6789'],
'Sweden' => [$sut, 'SE35 5000 0000 0549 1000 0003'],
'Switzerland' => [$sut, 'CH56 0483 5012 3456 7800 9'],
'Switzerland without spaces' => [$sut, 'CH9300762011623852957'],
'United Kingdom' => [$sut, 'GB98 MIDL 0700 9312 3456 78'],
];
}
/**
* {@inheritdoc}
*/
public function providerForInvalidInput(): array
{
$sut = new Iban();
return [
'Array' => [$sut, []],
'Number' => [$sut, 123456789],
'Bool' => [$sut, true],
'Object' => [$sut, new stdClass()],
'Empty' => [$sut, ''],
'Alpha' => [$sut, 'ABCDEFGHIKLMNOPQRSTVXYZ'],
'Symbols' => [$sut, '&"\'(-_)@-*/+.'],
'SwedenWrong' => [$sut, 'SE35 5000 5880 7742'],
'SwitzerlandWrong' => [$sut, 'CH93 5000 5880 7742'],
'HungaryWrong' => [$sut, 'HU42 5000 5880 7742'],
'GermanydWrong' => [$sut, 'DE89 5000 5880 7742'],
];
}
}