EDIT: I've mixed and modified two of the answers given below to form the full function which now does what I had wanted and then some... So I figured I'd post it here in case anyone else comes looking for this same thing.
/*
* Function to analyze string against many popular formatting styles of phone numbers
* Also breaks phone number into it's respective components
* 3-digit area code, 3-digit exchange code, 4-digit subscriber number
* After which it validates the 10 digit US number against NANPA guidelines
*/
function validPhone($phone) {
$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)))$/';
//Set array of variables to false initially
$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']) {
//Set array of new components
$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;
}
}
else {
$valid['ext'] = true;
}
}
//If the extension number is valid or non-existent, continue
if($valid['ext']) {
$valid['all'] = true;
}
}
return $valid['all'];
}
You can resolve this using a lookahead assertion. Basically what we're saying is I want a series of specific letters, (e, ex, ext, x, extension) followed by one or more number. But we also want to cover the case where there's no extension at all.
Side Note, you don't need brackets around single characters like [\s] or that [x] that follows. Also, you can group characters that are meant to be in the same spot, so instead of \s?\.?/?, you can use [\s\./]? which means "one of any of those characters"
Here's an update with regex that resolves your comment here as well. I've added the explanation in the actual code.
<?php
$sPattern = "/^
(?: # Area Code
(?:
\( # Open Parentheses
(?=\d{3}\)) # Lookahead. Only if we have 3 digits and a closing parentheses
)?
(\d{3}) # 3 Digit area code
(?:
(?<=\(\d{3}) # Closing Parentheses. Lookbehind.
\) # Only if we have an open parentheses and 3 digits
)?
[\s.\/-]? # Optional Space Delimeter
)?
(\d{3}) # 3 Digits
[\s\.\/-]? # Optional Space Delimeter
(\d{4})\s? # 4 Digits and an Optional following Space
(?: # Extension
(?: # Lets look for some variation of 'extension'
(?:
(?:e|x|ex|ext)\.? # First, abbreviations, with an optional following period
|
extension # Now just the whole word
)
\s? # Optionsal Following Space
)
(?=\d+) # This is the Lookahead. Only accept that previous section IF it's followed by some digits.
(\d+) # Now grab the actual digits (the lookahead doesn't grab them)
)? # The Extension is Optional
$/x"; // /x modifier allows the expanded and commented regex
$aNumbers = array(
'123-456-7890x123',
'123.456.7890x123',
'123 456 7890 x123',
'(123) 456-7890 x123',
'123.456.7890x.123',
'123.456.7890 ext. 123',
'123.456.7890 extension 123456',
'123 456 7890',
'123-456-7890ex123',
'123.456.7890 ex123',
'123 456 7890 ext123',
'456-7890',
'456 7890',
'456 7890 x123',
'1234567890',
'() 456 7890'
);
foreach($aNumbers as $sNumber) {
if (preg_match($sPattern, $sNumber, $aMatches)) {
echo 'Matched ' . $sNumber . "\n";
print_r($aMatches);
} else {
echo 'Failed ' . $sNumber . "\n";
}
}
?>
And The Output:
Matched 123-456-7890x123
Array
(
[0] => 123-456-7890x123
[1] => 123
[2] => 456
[3] => 7890
[4] => 123
)
Matched 123.456.7890x123
Array
(
[0] => 123.456.7890x123
[1] => 123
[2] => 456
[3] => 7890
[4] => 123
)
Matched 123 456 7890 x123
Array
(
[0] => 123 456 7890 x123
[1] => 123
[2] => 456
[3] => 7890
[4] => 123
)
Matched (123) 456-7890 x123
Array
(
[0] => (123) 456-7890 x123
[1] => 123
[2] => 456
[3] => 7890
[4] => 123
)
Matched 123.456.7890x.123
Array
(
[0] => 123.456.7890x.123
[1] => 123
[2] => 456
[3] => 7890
[4] => 123
)
Matched 123.456.7890 ext. 123
Array
(
[0] => 123.456.7890 ext. 123
[1] => 123
[2] => 456
[3] => 7890
[4] => 123
)
Matched 123.456.7890 extension 123456
Array
(
[0] => 123.456.7890 extension 123456
[1] => 123
[2] => 456
[3] => 7890
[4] => 123456
)
Matched 123 456 7890
Array
(
[0] => 123 456 7890
[1] => 123
[2] => 456
[3] => 7890
)
Matched 123-456-7890ex123
Array
(
[0] => 123-456-7890ex123
[1] => 123
[2] => 456
[3] => 7890
[4] => 123
)
Matched 123.456.7890 ex123
Array
(
[0] => 123.456.7890 ex123
[1] => 123
[2] => 456
[3] => 7890
[4] => 123
)
Matched 123 456 7890 ext123
Array
(
[0] => 123 456 7890 ext123
[1] => 123
[2] => 456
[3] => 7890
[4] => 123
)
Matched 456-7890
Array
(
[0] => 456-7890
[1] =>
[2] => 456
[3] => 7890
)
Matched 456 7890
Array
(
[0] => 456 7890
[1] =>
[2] => 456
[3] => 7890
)
Matched 456 7890 x123
Array
(
[0] => 456 7890 x123
[1] =>
[2] => 456
[3] => 7890
[4] => 123
)
Matched 1234567890
Array
(
[0] => 1234567890
[1] => 123
[2] => 456
[3] => 7890
)
Failed () 456 7890
The current REGEX
/^[\(]?(\d{0,3})[\)]?[\.]?[\/]?[\s]?[\-]?(\d{3})[\s]?[\.]?[\/]?[\-]?(\d{4})[\s]?[x]?(\d*)$/
has a lot of issues, resulting in it matching all of the following, among others:(0./ -000 ./-0000 x00000000000000000000000)
()./1234567890123456789012345678901234567890
\)\-555/1212 x
I think this REGEX is closer to what you're looking for:
/^(?:(?:(?:1[.\/\s-]?)(?!\())?(?:\((?=\d{3}\)))?((?(?!(37|96))[2-9][0-8][0-9](?<!(11)))?[2-9])(?:\((?<=\(\d{3}))?)?[.\/\s-]?([0-9]{2}(?<!(11)))[.\/\s-]?([0-9]{4}(?<!(555(01([0-9][0-9])|1212))))(?:[\s]*(?:(?:x|ext|extn|ex)[.:]*|extension[:]?)?[\s]*(\d+))?$/
or, exploded:
<?
$pattern =
'/^ # Matches from beginning of string
(?: # Country / Area Code Wrapper [not captured]
(?: # Country Code Wrapper [not captured]
(?: # Country Code Inner Wrapper [not captured]
1 # 1 - CC for United States and Canada
[.\/\s-]? # Character Class ('.', '/', '-' or whitespace) for allowed (optional, single) delimiter between Country Code and Area Code
) # End of Country Code
(?!\() # Lookahead, only allowed if not followed by an open parenthesis
)? # Country Code Optional
(?: # Opening Parenthesis Wrapper [not captured]
\( # Opening parenthesis
(?=\d{3}\)) # Lookahead, only allowed if followed by 3 digits and closing parenthesis [lookahead never captured]
)? # Parentheses Optional
((?(?!(37|96))[2-9][0-8][0-9](?<!(11)))?[2-9]) # 3-digit NANPA-valid Area Code [captured]
(?: # Closing Parenthesis Wrapper [not captured]
\( # Closing parenthesis
(?<=\(\d{3}) # Lookbehind, only allowed if preceded by 3 digits and opening parenthesis [lookbehind never captured]
)? # Parentheses Optional
)? # Country / Area Code Optional
[.\/\s-]? # Character Class ('.', '/', '-' or whitespace) for allowed (optional, single) delimiter between Area Code and Central-office Code
([0-9]{2}(?<!(11))) # 3-digit NANPA-valid Central-office Code [captured]
[.\/\s-]? # Character Class ('.', '/', '-' or whitespace) for allowed (optional, single) delimiter between Central-office Code and Subscriber number
([0-9]{4}(?<!(555(01([0-9][0-9])|1212)))) # 4-digit NANPA-valid Subscriber Number [captured]
(?: # Extension Wrapper [not captured]
[\s]* # Character Class for allowed delimiters (optional, multiple) between phone number and extension
(?: # Wrapper for extension description text [not captured]
(?:x|ext|extn|ex)[.:]* # Abbreviated extensions with character class for terminator (optional, multiple) [not captured]
| # OR
extension[:]? # The entire word extension with character class for optional terminator
)? # Marker for Extension optional
[\s]* # Character Class for allowed delimiters (optional, multiple) between extension description text and actual extension
(\d+) # Extension [captured if present], required for extension wrapper to match
)? # Entire extension optional
$ # Matches to end of string
/x'; // /x modifier allows the expanded and commented regex
?>
This modification provides several improvements.
preg_match()
.It has a few minor limitations. They're probably unimportant, but are being noted here.
The NANPA rules are adapted from the following REGEX, found here: http://blogchuck.com/2010/01/php-regex-for-validating-phone-numbers/
/^(?: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)))$/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With