3 * Zend Framework (http://framework.zend.com/)
5 * @link http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license http://framework.zend.com/license/new-bsd New BSD License
10 namespace Zend\Validator
;
13 use Zend\Stdlib\ArrayUtils
;
14 use Zend\Validator\AbstractValidator
;
15 use Zend\Validator\Exception
;
18 * Validates IBAN Numbers (International Bank Account Numbers)
20 class Iban
extends AbstractValidator
22 const NOTSUPPORTED
= 'ibanNotSupported';
23 const SEPANOTSUPPORTED
= 'ibanSepaNotSupported';
24 const FALSEFORMAT
= 'ibanFalseFormat';
25 const CHECKFAILED
= 'ibanCheckFailed';
28 * Validation failure message template definitions
32 protected $messageTemplates = array(
33 self
::NOTSUPPORTED
=> "Unknown country within the IBAN",
34 self
::SEPANOTSUPPORTED
=> "Countries outside the Single Euro Payments Area (SEPA) are not supported",
35 self
::FALSEFORMAT
=> "The input has a false IBAN format",
36 self
::CHECKFAILED
=> "The input has failed the IBAN check",
40 * Optional country code by ISO 3166-1
44 protected $countryCode;
47 * Optionally allow IBAN codes from non-SEPA countries. Defaults to true
51 protected $allowNonSepa = true;
54 * The SEPA country codes
56 * @var array<ISO 3166-1>
58 protected static $sepaCountries = array(
59 'AT', 'BE', 'BG', 'CY', 'CZ', 'DK', 'FO', 'GL', 'EE', 'FI', 'FR', 'DE',
60 'GI', 'GR', 'HU', 'IS', 'IE', 'IT', 'LV', 'LI', 'LT', 'LU', 'MT', 'MC',
61 'NL', 'NO', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'CH', 'GB'
65 * IBAN regexes by country code
69 protected static $ibanRegex = array(
70 'AD' => 'AD[0-9]{2}[0-9]{4}[0-9]{4}[A-Z0-9]{12}',
71 'AE' => 'AE[0-9]{2}[0-9]{3}[0-9]{16}',
72 'AL' => 'AL[0-9]{2}[0-9]{8}[A-Z0-9]{16}',
73 'AT' => 'AT[0-9]{2}[0-9]{5}[0-9]{11}',
74 'AZ' => 'AZ[0-9]{2}[A-Z]{4}[A-Z0-9]{20}',
75 'BA' => 'BA[0-9]{2}[0-9]{3}[0-9]{3}[0-9]{8}[0-9]{2}',
76 'BE' => 'BE[0-9]{2}[0-9]{3}[0-9]{7}[0-9]{2}',
77 'BG' => 'BG[0-9]{2}[A-Z]{4}[0-9]{4}[0-9]{2}[A-Z0-9]{8}',
78 'BH' => 'BH[0-9]{2}[A-Z]{4}[A-Z0-9]{14}',
79 'BR' => 'BR[0-9]{2}[0-9]{8}[0-9]{5}[0-9]{10}[A-Z][A-Z0-9]',
80 'CH' => 'CH[0-9]{2}[0-9]{5}[A-Z0-9]{12}',
81 'CR' => 'CR[0-9]{2}[0-9]{3}[0-9]{14}',
82 'CY' => 'CY[0-9]{2}[0-9]{3}[0-9]{5}[A-Z0-9]{16}',
83 'CZ' => 'CZ[0-9]{2}[0-9]{20}',
84 'DE' => 'DE[0-9]{2}[0-9]{8}[0-9]{10}',
85 'DO' => 'DO[0-9]{2}[A-Z0-9]{4}[0-9]{20}',
86 'DK' => 'DK[0-9]{2}[0-9]{14}',
87 'EE' => 'EE[0-9]{2}[0-9]{2}[0-9]{2}[0-9]{11}[0-9]{1}',
88 'ES' => 'ES[0-9]{2}[0-9]{4}[0-9]{4}[0-9]{1}[0-9]{1}[0-9]{10}',
89 'FI' => 'FI[0-9]{2}[0-9]{6}[0-9]{7}[0-9]{1}',
90 'FO' => 'FO[0-9]{2}[0-9]{4}[0-9]{9}[0-9]{1}',
91 'FR' => 'FR[0-9]{2}[0-9]{5}[0-9]{5}[A-Z0-9]{11}[0-9]{2}',
92 'GB' => 'GB[0-9]{2}[A-Z]{4}[0-9]{6}[0-9]{8}',
93 'GE' => 'GE[0-9]{2}[A-Z]{2}[0-9]{16}',
94 'GI' => 'GI[0-9]{2}[A-Z]{4}[A-Z0-9]{15}',
95 'GL' => 'GL[0-9]{2}[0-9]{4}[0-9]{9}[0-9]{1}',
96 'GR' => 'GR[0-9]{2}[0-9]{3}[0-9]{4}[A-Z0-9]{16}',
97 'GT' => 'GT[0-9]{2}[A-Z0-9]{4}[A-Z0-9]{20}',
98 'HR' => 'HR[0-9]{2}[0-9]{7}[0-9]{10}',
99 'HU' => 'HU[0-9]{2}[0-9]{3}[0-9]{4}[0-9]{1}[0-9]{15}[0-9]{1}',
100 'IE' => 'IE[0-9]{2}[A-Z]{4}[0-9]{6}[0-9]{8}',
101 'IL' => 'IL[0-9]{2}[0-9]{3}[0-9]{3}[0-9]{13}',
102 'IS' => 'IS[0-9]{2}[0-9]{4}[0-9]{2}[0-9]{6}[0-9]{10}',
103 'IT' => 'IT[0-9]{2}[A-Z]{1}[0-9]{5}[0-9]{5}[A-Z0-9]{12}',
104 'KW' => 'KW[0-9]{2}[A-Z]{4}[0-9]{22}',
105 'KZ' => 'KZ[0-9]{2}[0-9]{3}[A-Z0-9]{13}',
106 'LB' => 'LB[0-9]{2}[0-9]{4}[A-Z0-9]{20}',
107 'LI' => 'LI[0-9]{2}[0-9]{5}[A-Z0-9]{12}',
108 'LT' => 'LT[0-9]{2}[0-9]{5}[0-9]{11}',
109 'LU' => 'LU[0-9]{2}[0-9]{3}[A-Z0-9]{13}',
110 'LV' => 'LV[0-9]{2}[A-Z]{4}[A-Z0-9]{13}',
111 'MC' => 'MC[0-9]{2}[0-9]{5}[0-9]{5}[A-Z0-9]{11}[0-9]{2}',
112 'MD' => 'MD[0-9]{2}[A-Z0-9]{20}',
113 'ME' => 'ME[0-9]{2}[0-9]{3}[0-9]{13}[0-9]{2}',
114 'MK' => 'MK[0-9]{2}[0-9]{3}[A-Z0-9]{10}[0-9]{2}',
115 'MR' => 'MR13[0-9]{5}[0-9]{5}[0-9]{11}[0-9]{2}',
116 'MT' => 'MT[0-9]{2}[A-Z]{4}[0-9]{5}[A-Z0-9]{18}',
117 'MU' => 'MU[0-9]{2}[A-Z]{4}[0-9]{2}[0-9]{2}[0-9]{12}[0-9]{3}[A-Z]{3}',
118 'NL' => 'NL[0-9]{2}[A-Z]{4}[0-9]{10}',
119 'NO' => 'NO[0-9]{2}[0-9]{4}[0-9]{6}[0-9]{1}',
120 'PK' => 'PK[0-9]{2}[A-Z]{4}[A-Z0-9]{16}',
121 'PL' => 'PL[0-9]{2}[0-9]{8}[0-9]{16}',
122 'PS' => 'PS[0-9]{2}[A-Z]{4}[A-Z0-9]{21}',
123 'PT' => 'PT[0-9]{2}[0-9]{4}[0-9]{4}[0-9]{11}[0-9]{2}',
124 'RO' => 'RO[0-9]{2}[A-Z]{4}[A-Z0-9]{16}',
125 'RS' => 'RS[0-9]{2}[0-9]{3}[0-9]{13}[0-9]{2}',
126 'SA' => 'SA[0-9]{2}[0-9]{2}[A-Z0-9]{18}',
127 'SE' => 'SE[0-9]{2}[0-9]{3}[0-9]{16}[0-9]{1}',
128 'SI' => 'SI[0-9]{2}[0-9]{5}[0-9]{8}[0-9]{2}',
129 'SK' => 'SK[0-9]{2}[0-9]{4}[0-9]{6}[0-9]{10}',
130 'SM' => 'SM[0-9]{2}[A-Z]{1}[0-9]{5}[0-9]{5}[A-Z0-9]{12}',
131 'TN' => 'TN59[0-9]{2}[0-9]{3}[0-9]{13}[0-9]{2}',
132 'TR' => 'TR[0-9]{2}[0-9]{5}[A-Z0-9]{1}[A-Z0-9]{16}',
133 'VG' => 'VG[0-9]{2}[A-Z]{4}[0-9]{16}',
137 * Sets validator options
139 * @param array|Traversable $options OPTIONAL
141 public function __construct($options = array())
143 if ($options instanceof Traversable
) {
144 $options = ArrayUtils
::iteratorToArray($options);
147 if (array_key_exists('country_code', $options)) {
148 $this->setCountryCode($options['country_code']);
151 if (array_key_exists('allow_non_sepa', $options)) {
152 $this->setAllowNonSepa($options['allow_non_sepa']);
155 parent
::__construct($options);
159 * Returns the optional country code by ISO 3166-1
161 * @return string|null
163 public function getCountryCode()
165 return $this->countryCode
;
169 * Sets an optional country code by ISO 3166-1
171 * @param string|null $countryCode
172 * @return Iban provides a fluent interface
173 * @throws Exception\InvalidArgumentException
175 public function setCountryCode($countryCode = null)
177 if ($countryCode !== null) {
178 $countryCode = (string) $countryCode;
180 if (!isset(static::$ibanRegex[$countryCode])) {
181 throw new Exception\
InvalidArgumentException(
182 "Country code '{$countryCode}' invalid by ISO 3166-1 or not supported"
187 $this->countryCode
= $countryCode;
192 * Returns the optional allow non-sepa countries setting
196 public function allowNonSepa()
198 return $this->allowNonSepa
;
202 * Sets the optional allow non-sepa countries setting
204 * @param bool $allowNonSepa
205 * @return Iban provides a fluent interface
207 public function setAllowNonSepa($allowNonSepa)
209 $this->allowNonSepa
= (bool) $allowNonSepa;
214 * Returns true if $value is a valid IBAN
216 * @param string $value
219 public function isValid($value)
221 if (!is_string($value)) {
222 $this->error(self
::FALSEFORMAT
);
226 $value = str_replace(' ', '', strtoupper($value));
227 $this->setValue($value);
229 $countryCode = $this->getCountryCode();
230 if ($countryCode === null) {
231 $countryCode = substr($value, 0, 2);
234 if (!array_key_exists($countryCode, static::$ibanRegex)) {
235 $this->setValue($countryCode);
236 $this->error(self
::NOTSUPPORTED
);
240 if (!$this->allowNonSepa
&& !in_array($countryCode, static::$sepaCountries)) {
241 $this->setValue($countryCode);
242 $this->error(self
::SEPANOTSUPPORTED
);
246 if (!preg_match('/^' . static::$ibanRegex[$countryCode] . '$/', $value)) {
247 $this->error(self
::FALSEFORMAT
);
251 $format = substr($value, 4) . substr($value, 0, 4);
252 $format = str_replace(
253 array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
254 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'),
255 array('10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22',
256 '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35'),
260 $temp = intval(substr($format, 0, 1));
261 $len = strlen($format);
262 for ($x = 1; $x < $len; ++
$x) {
264 $temp +
= intval(substr($format, $x, 1));
269 $this->error(self
::CHECKFAILED
);