Validando o número de telefone dos EUA com php / regex

EDIT: Eu misturei e modifiquei duas das respostas dadas abaixo para formar a function completa que agora faz o que eu queria e depois alguns … Então eu pensei que eu iria publicá-lo aqui, caso alguém venha procurar o mesmo coisa.

/* * 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}(? 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']; } 

Você pode resolver isso usando uma asserção de lookahead . Basicamente, o que estamos dizendo é que eu quero uma série de letras específicas, (e, ex, ext, x, extensão), seguido de um ou mais números. Mas também queremos cobrir o caso em que não há extensão.

Nota lateral, você não precisa de colchetes em torno de caracteres únicos como [\ s] ou que [x] que se segue. Além disso, você pode agrupar caracteres que se destinam a estar no mesmo local, então, em vez de \ s? \.? / ?, você pode usar [\ s \ ./]? o que significa “um de qualquer desses personagens”

Aqui está uma atualização com o regex que resolve o seu comentário aqui também. Eu adicionei a explicação no código real.

 < ?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"; } } ?> 

E a Saída:

 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 

O REGEX atual

 /^[\(]?(\d{0,3})[\)]?[\.]?[\/]?[\s]?[\-]?(\d{3})[\s]?[\.]?[\/]?[\-]?(\d{4})[\s]?[x]?(\d*)$/ 

tem muitos problemas, resultando em coincidir com todos os seguintes, entre outros:
(0./ -000 ./-0000 x00000000000000000000000)
()./1234567890123456789012345678901234567890
\)\-555/1212 x

Eu acho que este REGEX está mais perto do que você está procurando:

 /^(?:(?:(?:1[.\/\s-]?)(?!\())?(?:\((?=\d{3}\)))?((?(?!(37|96))[2-9][0-8][0-9](?< !(11)))?[2-9])(?:\((?<=\(\d{3}))?)?[.\/\s-]?([0-9]{2}(? 

ou explodiu:

 < ? $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](? 

Esta modificação fornece várias melhorias.

  1. Ele cria um grupo configurável de itens que podem corresponder como a extensão. Você pode adicionar delimitadores adicionais para a extensão. Esta foi a solicitação original. A extensão também permite dois pontos após o delimitador de extensão.
  2. Ele converte a seqüência de 4 delimitadores opcionais (ponto, espaço em branco, barra ou hífen) em uma class de personagem que corresponde apenas a uma única.
  3. Agrupa os itens de forma adequada. No exemplo dado, você pode ter os parênteses de abertura sem um código de área entre eles, e você pode ter a marca de extensão (espaço-x) sem uma extensão. Esta expressão regular alternativa requer um código de área completo ou nenhum e uma extensão completa ou nenhuma.
  4. Os 4 componentes do número (código de área, código do escritório central, número de telefone e extensão) são os elementos referenciados que alimentam $ correspondências em preg_match() .
  5. Usa lookahead / lookbehind para exigir parênteses correspondentes no código de área.
  6. Permite que um 1 seja usado antes do número. (Isso pressupõe que todos os números são números dos EUA ou do Canadá, o que parece razoável uma vez que a partida é finalmente feita contra as restrições da NANPA. Também não permite a mistura do prefixo do código do país e do código de área enrolado entre parênteses.
  7. Ele se funde nas regras NANPA para eliminar números de telefone não atribuíveis.
    1. Ele elimina os códigos de área na forma 0xx, 1xx 37x, 96x, x9x e x11 que são códigos de área NANPA inválidos.
    2. Ele elimina os códigos do escritório central na forma 0xx e 1xx (códigos NANPA do escritório central inválidos).
    3. Elimina números com o formulário 555-01xx (não atribuível a NANPA).

Tem algumas pequenas limitações. Provavelmente não são importantes, mas estão sendo observados aqui.

  1. Não há nada no lugar para exigir que o mesmo delimitador seja usado repetidamente, permitindo números como 800-555.1212, 800/555 1212, 800 555.1212 etc.
  2. Não há nada no lugar para restringir o delimitador após um código de área com parênteses, permitindo números como (800) -555-1212 ou (800) / 5551212.

As regras NANPA são adaptadas do seguinte REGEX, encontrado aqui: 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}(? 

Por que não converter qualquer série de letras para ser “x”. Então, assim, você teria todas as possibilidades convertidas para ser “x”.

OU

Verifique se há 3digits, 3digits, 4digits, 1orMoreDigits e desconsidera quaisquer outros caracteres entre eles

Regex: ([0-9]{3}).*?([0-9]{3}).*?([0-9]{4}).+?([0-9]{1,})

Alternativamente, você poderia usar um JavaScript bastante simples e direto para forçar o usuário a entrar em um formato muito mais especificado. O plugin de input mascarada ( http://digitalbush.com/projects/masked-input-plugin/ ) para jQuery permite que você mascara uma input HTML como um número de telefone, permitindo apenas a pessoa inserir um número no formato xxx-xxx -xxxx. Não resolve os problemas de extensão, mas fornece uma experiência de usuário muito mais limpa.

Bem, você poderia modificar a regex, mas não será muito legal – você deve permitir “extn”? Que tal “extensão”? Que tal “e então você precisa discar”?

Eu acho que a maneira “correta” de fazer isso é adicionar uma checkbox de formulário de extensão, numérica e separada.

Mas se você realmente quer o regex, acho que o reparei. Dica: você não precisa de [x] para um único personagem, o x fará.

 /^\(?(\d{0,3})\)?(\.|\/)|\s|\-)?(\d{3})(\.|\/)|\s|\-)?(\d{4})\s?(x|ext)?(\d*)$/ 

Você permitiu um ponto, uma barra, um traço e um caractere de espaço em branco. Você deve permitir apenas uma dessas opções. Você precisará atualizar as referências para $matches ; Os grupos úteis agora são 0, 2 e 4.

PS Este não é testado, uma vez que não tenho uma implantação de referência de execução do PHP. Desculpas por erros, por favor me avise se você encontrar algum e tentarei consertá-los.

Editar

Isto é resumido muito melhor do que posso aqui .