Skip to content

Instantly share code, notes, and snippets.

@wpsmith
Created June 5, 2014 17:00
Show Gist options
  • Star 31 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • Save wpsmith/4541a05954aef58a59c5 to your computer and use it in GitHub Desktop.
Save wpsmith/4541a05954aef58a59c5 to your computer and use it in GitHub Desktop.
PHP: Sanitize & Format US Phone Numbers
<?php
function sanitize_phone( $phone, $international = false ) {
$format = "/(?:(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})(?:\s*(?:#|x\.?|ext\.?|extension)\s*(\d+))?$/";
$alt_format = '/^(\+\s*)?((0{0,2}1{1,3}[^\d]+)?\(?\s*([2-9][0-9]{2})\s*[^\d]?\s*([2-9][0-9]{2})\s*[^\d]?\s*([\d]{4})){1}(\s*([[:alpha:]#][^\d]*\d.*))?$/';
// Trim & Clean extension
$phone = trim( $phone );
$phone = preg_replace( '/\s+(#|x|ext(ension)?)\.?:?\s*(\d+)/', ' ext \3', $phone );
if ( preg_match( $alt_format, $phone, $matches ) ) {
return '(' . $matches[4] . ') ' . $matches[5] . '-' . $matches[6] . ( !empty( $matches[8] ) ? ' ' . $matches[8] : '' );
} elseif( preg_match( $format, $phone, $matches ) ) {
// format
$phone = preg_replace( $format, "($2) $3-$4", $phone );
// Remove likely has a preceding dash
$phone = ltrim( $phone, '-' );
// Remove empty area codes
if ( false !== strpos( trim( $phone ), '()', 0 ) ) {
$phone = ltrim( trim( $phone ), '()' );
}
// Trim and remove double spaces created
return preg_replace('/\\s+/', ' ', trim( $phone );
}
return false;
}
$array = array(
'-234-567-8901',
'1-234-567-8901',
'1-234-567-8901 x1234',
'1-234-567-8901 ext1234',
'1 (234) 567-8901',
'1.234.567.8901',
'1/234/567/8901',
'12345678901',
'(+351) 282 43 50 50',
'90191919908',
'555-8909',
'001 6867684',
'001 6867684x1',
'1 (234) 567-8901',
'1-234-567-8901 x1234',
'1-234-567-8901 ext1234',
'1-234 567.89/01 ext.1234',
'1(234)5678901x1234',
'(123)8575973',
'(0055)(123)8575973',
'(123)456-7890',
'(123)-456-7890',
'(123) 456-7890',
'(123) 456-7890 x123',
'12 1234 123 1 x1111',
'12 12 12 12 12',
'12 1 1234 123456 x12345',
'+12 1234 1234',
'+12 12 12 1234',
'+12 1234 5678',
'+12 12345678',
'(123)456789',
'(123456789',
'(123 456 789',
'(123.456.789',
'(123-456-789',
'123)456789',
'123) 456 789',
'123).456.789',
'123)-456-789',
);
foreach( $array as $phone ) {
echo $phone . ': ';
echo "\t\t" . sanitize_phone( $phone ) . "\n";
}
<?php
function validate_telephone_number($number, $formats) {
$format = trim(ereg_replace("[0-9]", "#", $number));
return (in_array($format, $formats)) ? true : false;
}
/* Usage Examples */
// List of possible formats: You can add new formats or modify the existing ones
$formats = array(
'###-###-####',
'####-###-###',
'(###) ###-###',
'####-####-####',
'##-###-####-####',
'####-####',
'###-###-###',
'#####-###-###',
'##########',
);
$numbers = array(
'08008-555-555',
'123-555-555',
'1800-1234-5678',
'(800) 555-123',
'1234567890',
);
foreach ( $numbers as $number ) {
if ( validate_telephone_number( $number, $formats ) ) {
echo $number.' is a valid phone number.';
echo "<br />";
}
}
function sanitize_phone( $phone, $international = false ) {
$phone = trim( $phone );
$phone = preg_replace( '/\s+(#|x|ext(ension)?)\.?:?\s*(\d+)/', ' ext \3', $phone );
$us_number = preg_match( '/^(\+\s*)?((0{0,2}1{1,3}[^\d]+)?\(?\s*([2-9][0-9]{2})\s*[^\d]?\s*([2-9][0-9]{2})\s*[^\d]?\s*([\d]{4})){1}(\s*([[:alpha:]#][^\d]*\d.*))?$/', $phone, $matches );
if ( $us_number ) {
return 'US: ' . $matches[4] . '-' . $matches[5] . '-' . $matches[6] . ( !empty( $matches[8] ) ? ' ' . $matches[8] : '' );
}
if ( ! $international ) {
/* SET ERROR: The field must be a valid U.S. phone number (e.g. 888-888-8888) */
return false;
}
$valid_number = preg_match( '/^(\+\s*)?(?=([.,\s()-]*\d){8})([\d(][\d.,\s()-]*)([[:alpha:]#][^\d]*\d.*)?$/', $phone, $matches ) && preg_match( '/\d{2}/', $phone );
if ( $valid_number ) {
return trim( $matches[1] ) . trim( $matches[3] ) . ( !empty( $matches[4] ) ? ' ' . $matches[4] : '' );
}
/* SET ERROR: The field must be a valid phone number (e.g. 888-888-8888) */
return false;
}
function formatPhoneNumber( $s ) {
$rx = "/
(1)?\D* # optional country code
(\d{3})?\D* # optional area code
(\d{3})\D* # first three
(\d{4}) # last four
(?:\D+|$) # extension delimiter or EOL
(\d*) # optional extension
/x";
preg_match($rx, $s, $matches);
if(!isset($matches[0])) return false;
$country = $matches[1];
$area = $matches[2];
$three = $matches[3];
$four = $matches[4];
$ext = $matches[5];
$out = "$three-$four";
if(!empty($area)) $out = "$area-$out";
if(!empty($country)) $out = "+$country-$out";
if(!empty($ext)) $out .= "x$ext";
// check that no digits were truncated
// if (preg_replace('/\D/', '', $s) != preg_replace('/\D/', '', $out)) return false;
return $out;
}
function format_phone_number ( $mynum, $mask ) {
/*********************************************************************/
/* Purpose: Return either masked phone number or false */
/* Masks: Val=1 or xxx xxx xxxx */
/* Val=2 or xxx xxx.xxxx */
/* Val=3 or xxx.xxx.xxxx */
/* Val=4 or (xxx) xxx xxxx */
/* Val=5 or (xxx) xxx.xxxx */
/* Val=6 or (xxx).xxx.xxxx */
/* Val=7 or (xxx) xxx-xxxx */
/* Val=8 or (xxx)-xxx-xxxx */
/*********************************************************************/
$val_num = validate_phone_number ( $mynum );
if ( !$val_num && !is_string ( $mynum ) ) {
echo "Number $mynum is not a valid phone number! \n";
return false;
} // end if !$val_num
if ( ( $mask == 1 ) || ( $mask == 'xxx xxx xxxx' ) ) {
$phone = preg_replace('~.*(\d{3})[^\d]*(\d{3})[^\d]*(\d{4}).*~',
'$1 $2 $3'." \n", $mynum);
return $phone;
} // end if $mask == 1
if ( ( $mask == 2 ) || ( $mask == 'xxx xxx.xxxx' ) ) {
$phone = preg_replace('~.*(\d{3})[^\d]*(\d{3})[^\d]*(\d{4}).*~',
'$1 $2.$3'." \n", $mynum);
return $phone;
} // end if $mask == 2
if ( ( $mask == 3 ) || ( $mask == 'xxx.xxx.xxxx' ) ) {
$phone = preg_replace('~.*(\d{3})[^\d]*(\d{3})[^\d]*(\d{4}).*~',
'$1.$2.$3'." \n", $mynum);
return $phone;
} // end if $mask == 3
if ( ( $mask == 4 ) || ( $mask == '(xxx) xxx xxxx' ) ) {
$phone = preg_replace('~.*(\d{3})[^\d]*(\d{3})[^\d]*(\d{4}).*~',
'($1) $2 $3'." \n", $mynum);
return $phone;
} // end if $mask == 4
if ( ( $mask == 5 ) || ( $mask == '(xxx) xxx.xxxx' ) ) {
$phone = preg_replace('~.*(\d{3})[^\d]*(\d{3})[^\d]*(\d{4}).*~',
'($1) $2.$3'." \n", $mynum);
return $phone;
} // end if $mask == 5
if ( ( $mask == 6 ) || ( $mask == '(xxx).xxx.xxxx' ) ) {
$phone = preg_replace('~.*(\d{3})[^\d]*(\d{3})[^\d]*(\d{4}).*~',
'($1).$2.$3'." \n", $mynum);
return $phone;
} // end if $mask == 6
if ( ( $mask == 7 ) || ( $mask == '(xxx) xxx-xxxx' ) ) {
$phone = preg_replace('~.*(\d{3})[^\d]*(\d{3})[^\d]*(\d{4}).*~',
'($1) $2-$3'." \n", $mynum);
return $phone;
} // end if $mask == 7
if ( ( $mask == 8 ) || ( $mask == '(xxx)-xxx-xxxx' ) ) {
$phone = preg_replace('~.*(\d{3})[^\d]*(\d{3})[^\d]*(\d{4}).*~',
'($1)-$2-$3'." \n", $mynum);
return $phone;
} // end if $mask == 8
return false; // Returns false if no conditions meet or input
} // end function format_phone_number
function validate_phone_number ( $phone ) {
/*********************************************************************/
/* Purpose: To determine if the passed string is a valid phone */
/* number following one of the establish formatting */
/* styles for phone numbers. This function also breaks */
/* a valid number into it's respective components of: */
/* 3-digit area code, */
/* 3-digit exchange code, */
/* 4-digit subscriber number */
/* and validates the number against 10 digit US NANPA */
/* guidelines. */
/*********************************************************************/
$format_pattern = '/^(?:(?:\((?=\d{3}\)))?(\d{3})(?:(?<=\(\d{3})\))'.
'?[\s.\/-]?)?(\d{3})[\s\.\/-]?(\d{4})\s?(?:(?:(?:'.
'(?:e|x|ex|ext)\.?\:?|extension\:?)\s?)(?=\d+)'.
'(\d+))?$/';
$nanpa_pattern = '/^(?:1)?(?(?!(37|96))[2-9][0-8][0-9](?<!(11)))?'.
'[2-9][0-9]{2}(?<!(11))[0-9]{4}(?<!(555(01([0-9]'.
'[0-9])|1212)))$/';
// Init array of variables to false
$valid = array('format' => false,
'nanpa' => false,
'ext' => false,
'all' => false);
//Check data against the format analyzer
if ( preg_match ( $format_pattern, $phone, $matchset ) ) {
$valid['format'] = true;
}
//If formatted properly, continue
//if($valid['format']) {
if ( !$valid['format'] ) {
return false;
} else {
//Set array of new components
$components = array ( 'ac' => $matchset[1], //area code
'xc' => $matchset[2], //exchange code
'sn' => $matchset[3] //subscriber number
);
// $components = array ( 'ac' => $matchset[1], //area code
// 'xc' => $matchset[2], //exchange code
// 'sn' => $matchset[3], //subscriber number
// 'xn' => $matchset[4] //extension number
// );
//Set array of number variants
$numbers = array ( 'original' => $matchset[0],
'stripped' => substr(preg_replace('[\D]', '', $matchset[0]), 0, 10)
);
//Now let's check the first ten digits against NANPA standards
if(preg_match($nanpa_pattern, $numbers['stripped'])) {
$valid['nanpa'] = true;
}
//If the NANPA guidelines have been met, continue
if ( $valid['nanpa'] ) {
if ( !empty ( $components['xn'] ) ) {
if ( preg_match ( '/^[\d]{1,6}$/', $components['xn'] ) ) {
$valid['ext'] = true;
} // end if if preg_match
} else {
$valid['ext'] = true;
} // end if if !empty
} // end if $valid nanpa
//If the extension number is valid or non-existent, continue
if ( $valid['ext'] ) {
$valid['all'] = true;
} // end if $valid ext
} // end if $valid
return $valid['all'];
} // end functon validate_phone_number
#0
^(?:(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})(?:\s*(?:#|x\.?|ext\.?|extension)\s*(\d+))?$
#1
1?\W*([2-9][0-8][0-9])\W*([2-9][0-9]{2})\W*([0-9]{4})(\se?x?t?(\d*))?
'1?\s*\W?\s*([2-9][0-8][0-9])\s*\W?\s*([2-9][0-9]{2})\s*\W?\s*([0-9]{4})(\se?x?t?(\d*))?';
print "match [$1-$2-$3]\n" if not defined $4;
print "match [$1-$2-$3 $5]\n" if defined $4;
#2
/^(?:(?:\(?(?:00|\+)([1-4]\d\d|[1-9]\d?)\)?)?[\-\.\ \\\/]?)?((?:\(?\d{1,}\)?[\-\.\ \\\/]?){0,})(?:[\-\.\ \\\/]?(?:#|ext\.?|extension|x)[\-\.\ \\\/]?(\d+))?$/i
#3
^((((\(\d{3}\))|(\d{3}-))\d{3}-\d{4})|(\+?\d{2}((-| )\d{1,8}){1,5}))(( x| ext)\d{1,5}){0,1}$
#4
^\d?(?:(?:[\+]?(?:[\d]{1,3}(?:[ ]+|[\-.])))?[(]?(?:[\d]{3})[\-/)]?(?:[ ]+)?)?(?:[a-zA-Z2-9][a-zA-Z0-9 \-.]{6,})(?:(?:[ ]+|[xX]|(i:ext[\.]?)){1,2}(?:[\d]{1,5}))?$
#5
$replace = array( ' ', '-', '/', '(', ')', ',', '.' ); //etc; as needed
preg_match( '/1?[0-9]{10}((ext|x)[0-9]{1,4})?/i', str_replace( $replace, '', $phone_num );
#6
^(\\(?\\d\\d\\d\\)?)( |-|\\.)?\\d\\d\\d( |-|\\.)?\\d{4,4}(( |-|\\.)?[ext\\.]+ ?\\d+)?$
#7
/\(?([0-9]{3})\)?([ .-]?)([0-9]{3})\2([0-9]{4})/
#8
^(\(?\d{3}\)?)([ .-])(\d{3})([ .-])(\d{4})$
#9
/\(?([0-9]{3})\)?([ .-]?)([0-9]{3})\2([0-9]{4})/
#10
/\(([0-9]{3})\)([ .-]?)([0-9]{3})\2([0-9]{4})|([0-9]{3})([ .-]?)([0-9]{3})\5([0-9]{4})/
#11
^(?:\([2-9]\d{2}\)\ ?|[2-9]\d{2}(?:\-?|\ ?))[2-9]\d{2}[- ]?\d{4}$
@aennisjr
Copy link

Thank you, great work. For the sanitize_phone.php you have a missing ')' closing parenthesis at line 30.

@johnabela
Copy link

johnabela commented Jun 22, 2018

+7 (777) 777-7777 ext 777

Fails to validate using sanitize_phone() within the sanitize_phone.php file.

It returns:

+7 (777) 777-7777 ext 777 :: +7 () 777-7777

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment