Проверка номера телефона в США с помощью php/regex

EDIT: Я смешал и изменил два из приведенных ниже ответов, чтобы сформировать полную функцию, которая теперь делает то, что я хотел, а затем некоторые... Поэтому я решил, что опубликую его здесь, в случае, если кто-то еще ищет то же самое.

/*
 * Function to analyze string against many popular formatting styles of phone numbers
 * Also breaks phone number into it 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 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'];
}

Ответ 1

Вы можете решить эту проблему с помощью lookahead assertion. В основном, что мы говорим, я хочу серию конкретных букв (e, ex, ext, x, extension), за которыми следует одно или несколько чисел. Но мы также хотим осветить случай отсутствия расширения.

Боковое примечание, вам не нужны скобки вокруг отдельных символов, таких как [\ s] или что [x] следует. Кроме того, вы можете группировать символы, которые должны быть в одном и том же то вместо \s? \.?/?, вы можете использовать [\ s \./]? что означает "один из символы"

Здесь обновляется регулярное выражение, которое также решает ваш комментарий. Я добавил объяснение в фактический код.

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

И выход:

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

Ответ 2

Текущий REGEX

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

имеет много проблем, в результате чего он сопоставляет все следующие, в том числе:
(0./ -000 ./-0000 x00000000000000000000000)
()./1234567890123456789012345678901234567890
\)\-555/1212 x

Я думаю, что этот REGEX ближе к тому, что вы ищете:

/^(?:(?:(?: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+))?$/

или, взорван:

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

?>

Эта модификация обеспечивает несколько улучшений.

  • Он создает настраиваемую группу элементов, которая может соответствовать расширению. Вы можете добавить дополнительные разделители для расширения. Это был исходный запрос. Расширение также допускает двоеточие после разграничения расширений.
  • Он преобразует последовательность из 4 необязательных разделителей (точка, пробел, косая черта или дефис) в класс символов, который соответствует только одному.
  • Он группирует элементы соответствующим образом. В данном примере вы можете иметь открывающие круглые скобки без кода области между ними, и вы можете иметь метку расширения (пробел-x) без расширения. Это альтернативное регулярное выражение требует либо полного кода области, либо ни одного, либо полного расширения, либо нет.
  • 4 компонента номера (код зоны, код центрального офиса, номер телефона и расширение) являются элементами обратной ссылки, которые передаются в $matches в preg_match().
  • Использование lookahead/lookbehind требует совпадающих круглых скобок в коде области.
  • Позволяет использовать 1 перед номером. (Это предполагает, что все номера являются номерами в США или Канаде, что кажется разумным, поскольку матч в конечном итоге делается против ограничений NANPA. Также запрещает смесь префикса кода страны и кода области, заключенного в круглые скобки.
  • Он объединяется в правила NANPA, чтобы устранить неприемлемые номера телефонов.
    • Он исключает коды областей в форме 0xx, 1xx 37x, 96x, x9x и x11, которые являются недействительными кодами областей NANPA.
    • Он исключает коды центрального офиса в форме 0xx и 1xx (недействительные коды центрального офиса NANPA).
    • Он исключает числа с формой 555-01xx (не назначается из NANPA).

У него есть несколько незначительных ограничений. Они, вероятно, неважны, но здесь отмечаются.

  • Нет ничего, что требовало бы, чтобы один и тот же разделитель использовался несколько раз, позволяя использовать цифры, такие как 800-555.1212, 800/555 1212, 800 555.1212 и т.д.
  • Нет никакого смысла ограничивать разделитель после кода области с помощью круглых скобок, что позволяет использовать числа (800) -555-1212 или (800)/5551212.

Правила NANPA адаптированы из следующего REGEX, найденного здесь: 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)))$/

Ответ 3

Почему бы не преобразовать любые строки букв в "x". Тогда вы сможете преобразовать все возможности в "x".

ИЛИ

Проверьте наличие 3digits, 3digits, 4digits, 1orMoreDigits и игнорируйте любые другие символы между ними

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

Ответ 4

В качестве альтернативы вы можете использовать довольно простой и простой JavaScript, чтобы заставить пользователя войти в гораздо более определенный формат. Masked Input Plugin (http://digitalbush.com/projects/masked-input-plugin/) для jQuery позволяет замаскировать ввод HTML как номер телефона, только позволяя человеку вводить номер в формате xxx-xxx-xxxx. Он не решает проблемы с расширением, но делает намного более понятный пользовательский интерфейс.

Ответ 5

Ну, вы можете изменить регулярное выражение, но это будет не очень приятно - разрешите ли вы "extn"? Как насчет "extentn"? Как насчет "а затем вам нужно набрать"?

Я думаю, что "правильный" способ сделать это - добавить отдельный, числовой, расширение формы.

Но если вы действительно хотите регулярное выражение, я думаю, что исправил его. Подсказка: вам не нужно [x] для одного символа, x будет делать.

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

Вы разрешили точку, косую черту, тире и символ пробела. Вы должны разрешить только один из этих вариантов. Вам нужно будет обновить ссылки на $matches; теперь полезными группами являются 0, 2 и 4.

P.S. Это непроверено, так как у меня нет ссылочного внедрения PHP. Извините за ошибки, пожалуйста, дайте мне знать, если вы найдете их, и я попытаюсь их исправить.

Изменить

Это суммируется намного лучше, чем я могу здесь.