IBAN Validator — PHP source
Validate International Bank Account Numbers (IBAN) with the mod-97 checksum, verify the country-specific length, and format the result. 100% client-side, no network.
This is the PHP implementation — the same logic the interactive tool runs, in a shareable, citable form.
<?php
/**
* Pure IBAN validation logic — ISO 13616 mod-97 checksum.
*
* Language: PHP
* CosmoDev polyglot showcase port of the `iban-validator` tool.
* Ported from src/lib/iban.ts — display source, part of CosmoDev's
* polyglot tool pages.
*
* Normalizes the input (uppercase, strip whitespace/dashes), checks the
* structural regex (country code + 2 check digits + 1-30 BBAN chars),
* verifies the per-country length, then runs the ISO 13616 mod-97
* checksum: move the first 4 chars to the end, map A=10..Z=35, and
* confirm the resulting integer is congruent to 1 mod 97. PHP's PCRE
* supports the lookahead used by the formatter, and the remainder stays
* under 97 throughout the fold, so plain ints suffice. validate_iban
* never throws — it reports failures through the 'error' key.
*/
declare(strict_types=1);
/**
* Per-country IBAN lengths (ISO 13616) — a representative subset.
*/
function iban_lengths(): array
{
return [
'AL' => 28, 'AD' => 24, 'AT' => 20, 'AZ' => 28, 'BH' => 22, 'BY' => 28,
'BE' => 16, 'BA' => 20, 'BR' => 29, 'BG' => 22, 'CR' => 22, '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, 'XK' => 20, 'KW' => 30, 'LV' => 21,
'LB' => 28, 'LI' => 21, 'LT' => 20, 'LU' => 20, 'MK' => 19, 'MT' => 31,
'MR' => 27, 'MU' => 30, 'MC' => 27, 'MD' => 24, 'ME' => 22, 'NL' => 18,
'NO' => 15, 'PK' => 24, 'PS' => 29, 'PL' => 28, 'PT' => 25, 'QA' => 29,
'RO' => 24, 'LC' => 32, 'SM' => 27, 'ST' => 25, 'SA' => 24, 'RS' => 22,
'SC' => 31, 'SK' => 24, 'SI' => 19, 'SG' => 19, 'ES' => 24, 'SE' => 24,
'CH' => 21, 'TL' => 23, 'TN' => 24, 'TR' => 26, 'UA' => 29, 'AE' => 23,
'GB' => 22, 'VG' => 24,
];
}
/**
* mod-97 checksum over a CLEANED iban (uppercase, no spaces/dashes).
*/
function mod97_check(string $cleaned): bool
{
// Move the first 4 chars (country + check) to the end.
$rearranged = substr($cleaned, 4) . substr($cleaned, 0, 4);
$numeric = '';
for ($i = 0, $n = strlen($rearranged); $i < $n; $i++) {
$code = ord($rearranged[$i]);
if ($code >= 48 && $code <= 57) {
$numeric .= $rearranged[$i];
} elseif ($code >= 65 && $code <= 90) {
$numeric .= (string)($code - 55); // A=10 .. Z=35
} else {
return false; // invalid character
}
}
$rem = 0;
for ($i = 0, $n = strlen($numeric); $i < $n; $i++) {
$rem = ($rem * 10 + (ord($numeric[$i]) - 48)) % 97;
}
return $rem === 1;
}
/**
* Validate an IBAN. Always returns an info array; never throws.
*
* @return array{input:string,cleaned:string,countryCode:?string,valid:bool,checksumOk:bool,lengthOk:bool,expectedLength:?int,formatted:string,error:?string}
*/
function validate_iban(string $input): array
{
$cleaned = strtoupper(preg_replace('/[\s-]/', '', $input) ?? '');
$cc = preg_match('/^[A-Z]{2}/', $cleaned) ? substr($cleaned, 0, 2) : null;
$lengths = iban_lengths();
$expected = ($cc !== null && isset($lengths[$cc])) ? $lengths[$cc] : null;
$info = [
'input' => $input,
'cleaned' => $cleaned,
'countryCode' => $cc,
'valid' => false,
'checksumOk' => false,
'lengthOk' => false,
'expectedLength' => $expected,
'formatted' => trim(preg_replace('/(.{4})(?=.)/', '$1 ', $cleaned) ?? ''),
'error' => null,
];
if (!preg_match('/^[A-Z]{2}[0-9]{2}[A-Z0-9]{1,30}$/', $cleaned)) {
$info['error'] = 'Invalid IBAN format.';
return $info;
}
$lengthOk = $expected === null ? true : strlen($cleaned) === $expected;
$checksumOk = mod97_check($cleaned);
$info['checksumOk'] = $checksumOk;
$info['lengthOk'] = $lengthOk;
if (!$lengthOk) {
$info['error'] = "Length should be {$expected} for {$cc}.";
return $info;
}
if (!$checksumOk) {
$info['error'] = 'Checksum failed.';
return $info;
}
$info['valid'] = true;
return $info;
}
// CLI demo: `php iban.php` validates a known-good reference IBAN.
var_export(validate_iban('GB82WEST12345698765432'));
echo PHP_EOL;
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →