Php форматирование номера телефона

Форматирование телефонных номеров на 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 и более цифр, но попадись ей номер из российской глубинки, она подавится и выдаст ошибочный результат.

Symphony, 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 );
>

// Perform phone number formatting here
if (strlen( $phone ) == 7 ) return preg_replace( «/([0-9a-zA-Z])([0-9a-zA-Z])/» , «$1-$2» , $phone );
> elseif (strlen( $phone ) == 10 ) return preg_replace( «/([0-9a-zA-Z])([0-9a-zA-Z])([0-9a-zA-Z])/» , «($1) $2-$3» , $phone );
> elseif (strlen( $phone ) == 11 ) return preg_replace( «/([0-9a-zA-Z])([0-9a-zA-Z])([0-9a-zA-Z])([0-9a-zA-Z])/» , «$1($2) $3-$4» , $phone );
>

// 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; — тот самый массив с информацией по всем странам.

+380(44)226-22-04
+380(44)226-22-04
+7(926)387-48-14
481-61-45
+44(0870)770-53-70
+44(0870)770-53-70
+43(6764)50-55-09
380(4878)415-46
380(5770)634-03
+380(44)244-12-01

Функция полностью решает поставленную задачу.
Из недостатков функции следует отметить отсутствие анализа медленных участков с целью оптимизаци, а также обработки телефонных номеров, где есть код города, но нет кода страны (в этом случае достаточно бить на блоки функцией phoneBlocks или воспользоваться одним из решений выше). При использовании ее в какой-либо реализации необходимо заменить глобальную переменную на ссылку в параметре, а также можно доработать или заменить формат вывода, за который отвечает функция phoneBlocks.

Быстродействие

Вопреки всем самым пессимистичным ожиданиям, код отрабатывает 10.000 номеров менее, чем за 2 секунды.

UPD Готовятся поправки:

  1. поддержка паттернов форматирования, принятых внутри конкретных стран («локально-принятые» нормы отображения номеров);
  2. добавление флага для указания, относительно какой страны выполнять форматирование номера;
  3. добавление параметра для указания формата вывода (в случае личных предпочтений и исключений);
  4. поддержка нелатинских буквенных номеров
  5. определение сотовых номеров и замена скобок на пробелы

Рекомендуем почитать

Источник

Сниппет для форматирования телефонных номеров

Задача. Есть большой список телефонных номеров, которые нужно переформатировать под единый формат (или форматы). Номера есть нескольких типов 7, 10 и 11 значные. Для каждого из этих типов необходимо вывести номер телефона в своем формате.

В php есть такие функции как money_format и number_format, но нет такой функции как phone_format, этот пробел я и решил восполнить написав такую функцию для форматирования телефонных номеров.

/** * Форматирование телефонного номера * по шаблону и маске для замены * * @param string $phone * @param string|array $format * @param string $mask * @return bool|string */ function phone_format($phone, $format, $mask = '#') < $phone = preg_replace('/[^0-9]/', '', $phone); if (is_array($format)) < if (array_key_exists(strlen($phone), $format)) < $format = $format[strlen($phone)]; >else < return false; >> $pattern = '/' . str_repeat('(1)?', substr_count($format, $mask)) . '(.*)/'; $format = preg_replace_callback( str_replace('#', $mask, '/([#])/'), function () use (&$counter) < return '$'; >, $format ); return ($phone) ? trim(preg_replace($pattern, $format, $phone, 1)) : false; > 

Использование

Передаем в качестве параметров не отформатированный номер телефона, шаблон и маску для замены. В качестве шаблона также можно передать массив ключи которого это количество цифр в телефоне, а значение шаблоны замены

$phones = array( '926 111-2233', '9261112233', '8 (926) 111 22 33', '8 926 111-22-33', '559-8833', '5598833', '', 'qweqwe' ); $formats = array( '7' => '###-##-##', '10' => '+7 (###) ### ####', '11' => '# (###) ### ####' ); foreach ($phones AS $phone)

Результат выполнения радует глаз:

+7 (926) 111 2233 +7 (926) 111 2233 8 (926) 111 2233 8 (926) 111 2233 559-88-33 559-88-33 

Данный сниппет не ставит перед собой цель определить город, регион или какой-либо другой параметр. Кроме того возникнут проблемы при использовании шаблонов для номеров “8 (123) 111-22-33” и “+7 (123) 111-22-33”. Возможно потом придумаю как поступать с такими номерами.

Источник

Читайте также:  SO question 343584
Оцените статью