- Validating a Phone Number in PHP
- In this article
- Validating for Digits Only
- Checking for Special Characters
- International Format
- Key Takeaways
- Форматирование телефонных номеров на PHP
- Всеуничтожающий примитив
- Форматирование с помощью sscanf
- Symfony, lib/helpers/PhoneHelper.php, format_phone
- Форматы телефонных номеров
- Самое интересное
- Быстродействие
Validating a Phone Number in PHP
In this short tutorial, we’re going to look at validating a phone number in PHP. Phone numbers come in many formats depending on the locale of the user. To cater for international users, we’ll have to validate against many different formats.
In this article
Validating for Digits Only
Let’s start with a basic PHP function to validate whether our input telephone number is digits only. We can then use our isDigits function to further refine our phone number validation.
We use the PHP preg_match function to validate the given telephone number using the regular expression:
This regular expression checks that the string $s parameter only contains digits 8 and has a minimum length $minDigits and a maximum length $maxDigits . You can find detailed information about the preg_match function in the PHP manual.
Checking for Special Characters
Next, we can check for special characters to cater for telephone numbers containing periods, spaces, hyphens and brackets .-() . This will cater for telephone numbers like:
The function isValidTelephoneNumber removes the special characters .-() then checks if we are left with digits only that has a minimum and maximum count of digits.
International Format
Our final validation is to cater for phone numbers in international format. We’ll update our isValidTelephoneNumber function to look for the + symbol. Our updated function will cater for numbers like:
tests whether the given telephone number starts with + and is followed by any digit 1 . If it passes that condition, we remove the + symbol and continue with the function as before.
Our final step is to normalize our telephone numbers so we can save all of them in the same format.
Key Takeaways
- Our code validates telephone numbers is various formats: numbers with spaces, hyphens and dots. We also considered numbers in international format.
- The validation code is lenient i.e: numbers with extra punctuation like 012.345-6789 will pass validation.
- Our normalize function removes extra punctuation but wont add a + symbol to our number if it doesn’t have it.
- You could update the validation function to be strict and update the normalize function to add the + symbol if desired.
This is the footer. If you’re reading this, it means you’ve reached the bottom of the page.
It would also imply that you’re interested in PHP, in which case, you’re in the right place.
We’d love to know what you think, so please do check back in a few days and hopefully the feedback form will be ready.
Форматирование телефонных номеров на PHP
Возникла задача автоматического форматирования телефонных номеров в виде страна (город) номер, и первым делом я обратился к существующим решениям.
К сожалению, оказалось, что все найденные решения основываются на обычном подгоне строки под пользовательский формат, имея ограниченную область применения и ошибки при выходе за ее пределы.
Для начала приведу обзор найденных решений. Тем, кому это не интересно, рекомендую прокрутить ниже до заголовка «Форматы телефонных номеров» — там уже представлен мой вариант разбора номера с ссылкой на код.
Всеуничтожающий примитив
(Найденное решение. Мое ниже)
Первое, на что я наткнулся — были сообщения на форумах и банки скриптов, предлагающие решения следующего плана:
function phone_number( $sPhone ) <
$sPhone = ereg_replace( «[^0-9]» , » , $sPhone );
if (strlen( $sPhone ) != 10 ) return (False);
$sArea = substr( $sPhone , 0 , 3 );
$sPrefix = substr( $sPhone , 3 , 3 );
$sNumber = substr( $sPhone , 6 , 4 );
$sPhone = «(» . $sArea . «)» . $sPrefix . «-» . $sNumber ;
return ( $sPhone );
>
?>
Один из простых вариантов шустрого форматирования телефонных номеров, но каждое такое решение ориентировано на телефонные номера из конкретной локальной зоны и не является решением задачи.
Форматирование с помощью sscanf
function formatPhone( $phone ) if ( empty ( $phone )) return «» ;
if (strlen( $phone ) == 7 )
sscanf( $phone , «%3s%4s» , $prefix , $exchange );
else if (strlen( $phone ) == 10 )
sscanf( $phone , «%3s%3s%4s» , $area , $prefix , $exchange );
else if (strlen( $phone ) > 10 )
if (substr( $phone , 0 , 1 )== ‘1’ ) sscanf( $phone , «%1s%3s%3s%4s» , $country , $area , $prefix , $exchange );
>
else sscanf( $phone , «%3s%3s%4s%s» , $area , $prefix , $exchange , $extension );
>
else
return «unknown phone format: $phone» ;
$out = «» ;
$out .= isset ( $country )? $country . ‘ ‘ : » ;
$out .= isset ( $area )? ‘(‘ . $area . ‘) ‘ : » ;
$out .= $prefix . ‘-‘ . $exchange ;
$out .= isset ( $extension )? ‘ x’ . $extension : » ;
return $out ;
>
Не смотря на простое решение, эта функция уже умеет форматировать номера длиной 7, 10 и более цифр, но попадись ей номер из российской глубинки, она подавится и выдаст ошибочный результат.
Symfony, lib/helpers/PhoneHelper.php, format_phone
function format_phone( $phone = » , $convert = false , $trim = true )
// If we have not entered a phone number just return empty
if ( empty ( $phone )) return » ;
>
// Strip out any extra characters that we do not need only keep letters and numbers
$phone = preg_replace( «/[^0-9A-Za-z]/» , «» , $phone );
// Do we want to convert phone numbers with letters to their number equivalent?
// Samples are: 1-800-TERMINIX, 1-800-FLOWERS, 1-800-Petmeds
if ( $convert == true ) $replace = array ( ‘2’ => array ( ‘a’ , ‘b’ , ‘c’ ),
‘3’ => array ( ‘d’ , ‘e’ , ‘f’ ),
‘4’ => array ( ‘g’ , ‘h’ , ‘i’ ),
‘5’ => array ( ‘j’ , ‘k’ , ‘l’ ),
‘6’ => array ( ‘m’ , ‘n’ , ‘o’ ),
‘7’ => array ( ‘p’ , ‘q’ , ‘r’ , ‘s’ ),
‘8’ => array ( ‘t’ , ‘u’ , ‘v’ ), ‘9’ => array ( ‘w’ , ‘x’ , ‘y’ , ‘z’ ));
// Replace each letter with a number
// Notice this is case insensitive with the str_ireplace instead of str_replace
foreach ( $replace as $digit => $letters ) $phone = str_ireplace( $letters , $digit , $phone );
>
>
// If we have a number longer than 11 digits cut the string down to only 11
// This is also only ran if we want to limit only to 11 characters
if ( $trim == true && strlen( $phone )> 11 ) $phone = substr( $phone , 0 , 11 );
>
// Return original phone if not 7, 10 or 11 digits long
return $phone ;
>
?>
Функция позволяет не только форматировать в XXX-XXXX, (XXX) XXX-XXXX и X (XXX) XXX-XXXX, но и конвертировать номера, написанные цифрами. Ограниченность функции в форматировании номеров длиной 7, 10 и 11 символов никак не подходит.
Форматы телефонных номеров
Из вики-статьи видно, что никакого простого и удобного паттерна для быстрого форматирования всех номеров не существует. Коды стран регистрируются, подобно доменным зонам, а коды городов — остаются на совести каждой из стран.
Другими словами, маршрутизация звонков идет по маске, начиная с кода страны: звонок, направленный в конкретную страну далее пробивает себе маршрут в соответствии с кодами области, города, района и т.д. начиная с самой левой цифры, пока последнее звено не перебросит его на конкретный телефонный/факсовый аппарат. Проблема усложняется еще и тем, что коды городов внутри стран точно так же не поддаются единой сквозной стандартизации, т.е. в худшем из вариантов для правильного форматирования номеров придется использовать двумерный массив с кодами стран и их городов.
На самом деле, все оказалось не так страшно. В каждой стране можно разделить все коды городов на две части: на те, что в большинстве своем совпадают по длине, и все остальные. Этого достаточно, чтобы резко сократить область перебора кодов при сравнении. Т.е. можно создать массив из данных по каждой стране вида:
$data = Array(
‘Код страны’ =>Array(
‘name’ => ‘Имя страны’ , // для удобства. Не будет использоваться.
‘cityCodeLength’ => обычная_длина_кода_города_для_этой_страны,
‘exceptions’ =>Array(коды_городов_исключения),
)
);
?>
Затем провести предварительную обработку данных, дополнив его полями, сужающими область перебора, exceptions_max и exceptions_min — максимальной и минимальной длиной кода городов-исключений, соответственно. Также необходимо учесть страны, в которых коды городов начинаются на 0 — отразим эту «особенность» полем zeroHack. Как пример:
$data = Array(
‘886’ =>Array(
‘name’ => ‘Taiwan’ ,
‘cityCodeLength’ => 1 ,
‘zeroHack’ => false ,
‘exceptions’ =>Array( 89 , 90 , 91 , 92 , 93 , 96 , 60 , 70 , 94 , 95 ),
‘exceptions_max’ => 2 ,
‘exceptions_min’ => 2
),
);
?>
function phone( $phone = » , $convert = true , $trim = true )
global $phoneCodes ; // только для примера! При реализации избавиться от глобальной переменной.
if ( empty ( $phone )) return » ;
>
// очистка от лишнего мусора с сохранением информации о «плюсе» в начале номера
$phone =trim( $phone );
$plus = ( $phone [ 0 ] == ‘+’ );
$phone = preg_replace( «/[^0-9A-Za-z]/» , «» , $phone );
$OriginalPhone = $phone ;
foreach ( $replace as $digit => $letters ) $phone = str_ireplace( $letters , $digit , $phone );
>
>
// заменяем 00 в начале номера на +
if (substr( $phone , 0 , 2 )== «00» )
$phone = substr( $phone , 2 , strlen( $phone )- 2 );
$plus = true ;
>
// если телефон длиннее 7 символов, начинаем поиск страны
if (strlen( $phone )> 7 )
foreach ( $phoneCodes as $countryCode => $data )
$codeLen = strlen( $countryCode );
if (substr( $phone , 0 , $codeLen )== $countryCode )
// как только страна обнаружена, урезаем телефон до уровня кода города
$phone = substr( $phone , $codeLen , strlen( $phone )- $codeLen );
$zero = false ;
// проверяем на наличие нулей в коде города
if ( $data [ ‘zeroHack’ ] && $phone [ 0 ]== ‘0’ )
$zero = true ;
$phone = substr( $phone , 1 , strlen( $phone )- 1 );
>
$cityCode =NULL;
// сначала сравниваем с городами-исключениями
if ( $data [ ‘exceptions_max’ ]!= 0 )
for ( $cityCodeLen = $data [ ‘exceptions_max’ ]; $cityCodeLen >= $data [ ‘exceptions_min’ ]; $cityCodeLen —)
if (in_array(intval(substr( $phone , 0 , $cityCodeLen )), $data [ ‘exceptions’ ]))
$cityCode = ( $zero ? «0» : «» ).substr( $phone , 0 , $cityCodeLen );
$phone = substr( $phone , $cityCodeLen , strlen( $phone )- $cityCodeLen );
break ;
>
// в случае неудачи с исключениями вырезаем код города в соответствии с длиной по умолчанию
if (is_null( $cityCode ))
$cityCode = substr( $phone , 0 , $data [ ‘cityCodeLength’ ]);
$phone = substr( $phone , $data [ ‘cityCodeLength’ ], strlen( $phone )- $data [ ‘cityCodeLength’ ]);
>
// возвращаем результат
return ( $plus ? «+» : «» ). $countryCode . ‘(‘ . $cityCode . ‘)’ .phoneBlocks( $phone );
>
>
// возвращаем результат без кода страны и города
return ( $plus ? «+» : «» ).phoneBlocks( $phone );
>
// функция превращает любое число в строку формата XX-XX-. или XXX-XX-XX-. в зависимости от четности кол-ва цифр
function phoneBlocks( $number ) $add = » ;
if (strlen( $number )% 2 )
$add = $number [ 0 ];
$add .= (strlen( $number ) $number = substr( $number , 1 , strlen( $number )- 1 );
>
return $add .implode( «-» , str_split( $number , 2 ));
>
// тесты
echo phone( «+38 (044) 226-22-04» ). «
» ;
echo phone( «0038 (044) 226-22-04» ). «
» ;
echo phone( «+79263874814» ). «
» ;
echo phone( «4816145» ). «
» ;
echo phone( «+44 (0) 870 770 5370» ). «
» ;
echo phone( «0044 (0) 870 770 5370» ). «
» ;
echo phone( «+436764505509» ). «
» ;
echo phone( «(+38-048) 784-15-46 » ). «
» ;
echo phone( «(38-057) 706-34-03 » ). «
» ;
echo phone( «+38 (044) 244 12 01 » ). «
» ;
?>
, где global $phoneCodes; — тот самый массив с информацией по всем странам.
Функция полностью решает поставленную задачу.
Из недостатков функции следует отметить отсутствие анализа медленных участков с целью оптимизаци, а также обработки телефонных номеров, где есть код города, но нет кода страны (в этом случае достаточно бить на блоки функцией phoneBlocks или воспользоваться одним из решений выше). При использовании ее в какой-либо реализации необходимо заменить глобальную переменную на ссылку в параметре, а также можно доработать или заменить формат вывода, за который отвечает функция phoneBlocks.
Самое интересное
Используя информацию с сайтов:
http://www.mtt.ru/info/def/index.wbp
http://www.hella.ru/code/codeuro.htm
http://www.scross.ru/guide/phone-global/
я собрал массив данных по всем представленным странам, включая города-исключения, флаги zeroHack, а также коды мобильных сетей. Код можно загрузить здесь.
Быстродействие
Вопреки всем самым пессимистичным ожиданиям, код отрабатывает 10.000 номеров менее чем за 2 секунды.
- поддержка паттернов форматирования, принятых внутри конкретных стран («локально-принятые» нормы отображения номеров);
- добавление флага для указания, относительно какой страны выполнять форматирование номера;
- добавление параметра для указания формата вывода (в случае личных предпочтений и исключений);
- поддержка нелатинских буквенных номеров
- определение сотовых номеров и замена скобок на пробелы