Iban.php 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. <?php
  2. /*
  3. * Copyright (c) Alexandre Gomes Gaigalas <alganet@gmail.com>
  4. * SPDX-License-Identifier: MIT
  5. */
  6. declare(strict_types=1);
  7. namespace Respect\Validation\Rules;
  8. use function bcmod;
  9. use function is_string;
  10. use function ord;
  11. use function preg_match;
  12. use function preg_replace_callback;
  13. use function str_replace;
  14. use function strlen;
  15. use function strval;
  16. use function substr;
  17. /**
  18. * Validates whether the input is a valid IBAN (International Bank Account Number) or not.
  19. *
  20. * @author Mazen Touati <mazen_touati@hotmail.com>
  21. */
  22. final class Iban extends AbstractRule
  23. {
  24. private const COUNTRIES_LENGTHS = [
  25. 'AL' => 28,
  26. 'AD' => 24,
  27. 'AT' => 20,
  28. 'AZ' => 28,
  29. 'BH' => 22,
  30. 'BE' => 16,
  31. 'BA' => 20,
  32. 'BR' => 29,
  33. 'BG' => 22,
  34. 'CR' => 21,
  35. 'HR' => 21,
  36. 'CY' => 28,
  37. 'CZ' => 24,
  38. 'DK' => 18,
  39. 'DO' => 28,
  40. 'EE' => 20,
  41. 'FO' => 18,
  42. 'FI' => 18,
  43. 'FR' => 27,
  44. 'GE' => 22,
  45. 'DE' => 22,
  46. 'GI' => 23,
  47. 'GR' => 27,
  48. 'GL' => 18,
  49. 'GT' => 28,
  50. 'HU' => 28,
  51. 'IS' => 26,
  52. 'IE' => 22,
  53. 'IL' => 23,
  54. 'IT' => 27,
  55. 'JO' => 30,
  56. 'KZ' => 20,
  57. 'KW' => 30,
  58. 'LV' => 21,
  59. 'LB' => 28,
  60. 'LI' => 21,
  61. 'LT' => 20,
  62. 'LU' => 20,
  63. 'MK' => 19,
  64. 'MT' => 31,
  65. 'MR' => 27,
  66. 'MU' => 30,
  67. 'MD' => 24,
  68. 'MC' => 27,
  69. 'ME' => 22,
  70. 'NL' => 18,
  71. 'NO' => 15,
  72. 'PK' => 24,
  73. 'PL' => 28,
  74. 'PS' => 29,
  75. 'PT' => 25,
  76. 'QA' => 29,
  77. 'XK' => 20,
  78. 'RO' => 24,
  79. 'LC' => 32,
  80. 'SM' => 27,
  81. 'ST' => 25,
  82. 'SA' => 24,
  83. 'RS' => 22,
  84. 'SC' => 31,
  85. 'SK' => 24,
  86. 'SI' => 19,
  87. 'ES' => 24,
  88. 'SE' => 24,
  89. 'CH' => 21,
  90. 'TL' => 23,
  91. 'TN' => 24,
  92. 'TR' => 26,
  93. 'UA' => 29,
  94. 'AE' => 23,
  95. 'GB' => 22,
  96. 'VG' => 24,
  97. ];
  98. /**
  99. * {@inheritDoc}
  100. */
  101. public function validate($input): bool
  102. {
  103. if (!is_string($input)) {
  104. return false;
  105. }
  106. $iban = str_replace(' ', '', $input);
  107. if (!preg_match('/[A-Z0-9]{15,34}/', $iban)) {
  108. return false;
  109. }
  110. $countryCode = substr($iban, 0, 2);
  111. if (!$this->hasValidCountryLength($iban, $countryCode)) {
  112. return false;
  113. }
  114. $checkDigits = substr($iban, 2, 2);
  115. $bban = substr($iban, 4);
  116. $rearranged = $bban . $countryCode . $checkDigits;
  117. return bcmod($this->convertToIntegerAsString($rearranged), '97') === '1';
  118. }
  119. private function hasValidCountryLength(string $iban, string $countryCode): bool
  120. {
  121. if (!isset(self::COUNTRIES_LENGTHS[$countryCode])) {
  122. return false;
  123. }
  124. return strlen($iban) === self::COUNTRIES_LENGTHS[$countryCode];
  125. }
  126. private function convertToIntegerAsString(string $reArrangedIban): string
  127. {
  128. return (string) preg_replace_callback(
  129. '/[A-Z]/',
  130. static function (array $match): string {
  131. return strval(ord($match[0]) - 55);
  132. },
  133. $reArrangedIban
  134. );
  135. }
  136. }