⬆️ ⬇️

Formatting phone numbers in PHP

The problem arose of automatically formatting telephone numbers in the form of a country (city) number , and first of all I turned to existing solutions.

Unfortunately, it turned out that all the solutions found are based on the usual fitting of a string to a custom format, having limited scope and errors when going beyond its limits.



First, I will give an overview of the solutions found. For those who are not interested, I recommend scrolling below to the heading “Telephone Number Formats” - my version of the number analysis is already presented there with a link to the code.



All-Destructive Primitive

(Found solution. Mine below)

The first thing I stumbled upon were messages on forums and script banks offering solutions for the following plan:

<?

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

}

?>


One of the simplest options for smartly formatting phone numbers, but each such solution is focused on phone numbers from a specific local zone and is not a solution to the problem.

')

Formatting with sscanf

(Found solution. Mine below)

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 ;

}


Despite the simple solution, this function is already able to format numbers with a length of 7, 10 or more digits, but if it gets a number from the Russian backland, it will choke and give an erroneous result.



Symfony, lib / helpers / PhoneHelper.php, format_phone

(Found solution. Mine below)

<? php

function format_phone ( $ phone = '' , $ convert = false , $ trim = true )

{

// If you have not entered

if ( empty ( $ phone )) {

return '' ;

}



// Keep out the letters and numbers

$ phone = preg_replace ( "/ [^ 0-9A-Za-z] /" , "" , $ phone );



// Do you want to convert?

// 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 an order.

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] {3}) ([0-9a-zA-Z] {4}) /" , "$ 1- $ 2" , $ phone );

} elseif ( strlen ( $ phone ) == 10 ) {

return preg_replace ( "/ ([0-9a-zA-Z] {3}) ([0-9a-zA-Z] {3}) ([0-9a-zA-Z] {4}) /" , "($ 1) $ 2- $ 3" , $ phone );

} elseif ( strlen ( $ phone ) == 11 ) {

return preg_replace ( "/ ([0-9a-zA-Z] {1}) ([0-9a-zA-Z] {3}) ([0-9a-zA-Z] {3}) ([0 -9a-zA-Z] {4}) / " , " $ 1 ($ 2) $ 3- $ 4 " , $ phone );

}



// Return original phone if not 7, 10 or 11 digits long

return $ phone ;

}

?>


The function allows not only formatting in XXX-XXXX, (XXX) XXX-XXXX and X (XXX) XXX-XXXX, but also convert numbers written in numbers. The limited function in formatting numbers of length 7, 10 and 11 characters does not fit.



Phone Number Formats

From the wiki article, it is clear that there is no simple and convenient pattern for fast formatting of all numbers. Country codes are registered, like domain zones, and city codes remain on the conscience of each country.



In other words, call routing follows a mask, starting with a country code: a call directed to a specific country further punches a route in accordance with the codes of the region, city, region, etc. starting from the leftmost digit until the last link transfers it to a specific telephone / fax machine. The problem is further complicated by the fact that the codes of cities within countries likewise defy unified end-to-end standardization, i.e. in the worst of cases, to correctly format the numbers, you will have to use a two-dimensional array with the codes of countries and their cities.



In fact, everything was not so scary. In each country, you can divide all the codes of cities into two parts: those that mostly coincide in length, and all the rest. This is enough to drastically reduce the code search area when comparing. Those. You can create an array of data for each country of the form:

<?

$ data = Array (

'Country Code' => Array (

'name' => 'Country Name' , // for convenience. Will not be used.

'cityCodeLength' => regular_length_code_city_for_this_country,

'exceptions' => Array (city_exclusion_ codes),

)

);

?>

Then, pre-process the data, complementing it with fields narrowing the brute force area, exceptions_max and exceptions_min - with the maximum and minimum code of the cities of exceptions, respectively. It is also necessary to take into account countries in which city codes start with 0 - we will reflect this “feature” by the zeroHack field. As an example:
<?

$ 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

),

);

?>
After that, we take the appropriate sections of the code from the solutions above and make the formatting function:
<?

function phone ( $ phone = '' , $ convert = true , $ trim = true )

{

global $ phoneCodes ; // for example only! When implemented, get rid of the global variable.

if ( empty ( $ phone )) {

return '' ;

}

// cleaning of excess garbage with saving information about the "plus" at the beginning of the number

$ phone = trim ( $ phone );

$ plus = ( $ phone [ 0 ] == '+' );

$ phone = preg_replace ( "/ [^ 0-9A-Za-z] /" , "" , $ phone );

$ OriginalPhone = $ phone ;



// convert the letter number to digital

if ( $ convert == true &&! is_numeric ( $ phone )) {

$ 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' ));



foreach ( $ replace as $ digit => $ letters ) {

$ phone = str_ireplace ( $ letters , $ digit , $ phone );

}

}



// replace 00 at the beginning of the number with +

if ( substr ( $ phone , 0 , 2 ) == "00" )

{

$ phone = substr ( $ phone , 2 , strlen ( $ phone ) - 2 );

$ plus = true ;

}



// if the phone is longer than 7 characters, start the search for the country

if ( strlen ( $ phone )> 7 )

foreach ( $ phoneCodes as $ countryCode => $ data )

{

$ codeLen = strlen ( $ countryCode );

if ( substr ( $ phone , 0 , $ codeLen ) == $ countryCode )

{

// as soon as the country is detected, we cut the phone down to the level of the city code

$ phone = substr ( $ phone , $ codeLen , strlen ( $ phone ) - $ codeLen );

$ zero = false ;

// check for the presence of zeros in the city code

if ( $ data [ 'zeroHack' ] && $ phone [ 0 ] == '0' )

{

$ zero = true ;

$ phone = substr ( $ phone , 1 , strlen ( $ phone ) - 1 );

}



$ cityCode = NULL;

// first compare to exception cities

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 ;

}

// in case of failure with exceptions, cut the area code in accordance with the default length

if ( is_null ( $ cityCode ))

{

$ cityCode = substr ( $ phone , 0 , $ data [ 'cityCodeLength' ]);

$ phone = substr ( $ phone , $ data [ 'cityCodeLength' ], strlen ( $ phone ) - $ data [ 'cityCodeLength' ]);

}

// return the result

return ( $ plus ? "+" : "" ). $ countryCode . '(' . $ cityCode . ')' .phoneBlocks ( $ phone );

}

}

// return the result without a country and city code

return ( $ plus ? "+" : "" ) .phoneBlocks ( $ phone );

}



// the function turns any number into a string of format XX-XX -... or XXX-XX-XX -... depending on the parity of the number of digits

function phoneBlocks ( $ number ) {

$ add = '' ;

if ( strlen ( $ number )% 2 )

{

$ add = $ number [ 0 ];

$ add . = ( strlen ( $ number ) <= 5? "-": "");

$ number = substr ( $ number , 1 , strlen ( $ number ) - 1 );

}

return $ add . implode ( "-" , str_split ( $ number , 2 ));

}



// tests

echo phone ( "+38 (044) 226-22-04" ). "<br />" ;

echo phone ( "0038 (044) 226-22-04" ). "<br />" ;

echo phone ( "+79263874814" ). "<br />" ;

echo phone ( "4816145" ). "<br />" ;

echo phone ( "+44 (0) 870 770 5370" ). "<br />" ;

echo phone ( "0044 (0) 870 770 5370" ). "<br />" ;

echo phone ( "+436764505509" ). "<br />" ;

echo phone ( "(+ 38-048) 784-15-46" ). "<br />" ;

echo phone ( "(38-057) 706-34-03" ). "<br />" ;

echo phone ( "+38 (044) 244 12 01" ). "<br />" ;

?>


where global $ phoneCodes; - the same array of information for all countries.



Will output

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


The function completely solves the task.

Among the shortcomings of the function, it is necessary to note the lack of analysis of slow areas for the purpose of optimization, as well as the processing of telephone numbers, where there is a city code, but no country code (in this case it is enough to beat the blocks with the phoneBlocks function or use one of the solutions above). When using it in any implementation, it is necessary to replace the global variable with a reference in the parameter, and you can also refine or replace the output format for which the phoneBlocks function is responsible .



The most interesting

Using information from sites:

http://www.mtt.ru/info/def/index.wbp

http://www.hella.ru/code/codeuro.htm

http://www.scross.ru/guide/phone-global/

I collected an array of data for all the countries represented, including exception cities, zeroHack flags, and mobile network codes. The code can be downloaded here .



Speed ​​performance

Contrary to all the most pessimistic expectations, the code fulfills 10,000 numbers in less than 2 seconds.



UPD Preparing amendments:
  1. support for formatting patterns adopted within specific countries (“locally-accepted” number mapping norms);
  2. adding a flag to indicate which country to format the number for;
  3. adding a parameter to specify the output format (in the case of personal preferences and exceptions);
  4. support for non-Latin letter numbers
  5. determining cell numbers and replacing brackets with spaces
UPD: Archive disappeared from the server, posted on https://github.com/mrXCray/PhoneCodes Soon there will be an update on the amendments above + bonus.

Source: https://habr.com/ru/post/102352/



All Articles