【问题标题】:Validating US phone number with php/regex使用 php/regex 验证美国电话号码
【发布时间】:2011-03-22 10:09:10
【问题描述】:

编辑: 我已经混合并修改了下面给出的两个答案,以形成完整的功能,现在可以完成我想要的功能,然后......所以我想我' d张贴在这里,以防其他人来寻找同样的东西。

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

【问题讨论】:

  • 我觉得有问题。您的格式允许区号是可选的,但是 nanpa 模式(我认为是正确的)需要有一个正确的区号。此外,如果没有给出区号,但有 给出了扩展名,该怎么办。当您剥离原始号码时,您会删除非数字,然后盲目地抓取包含扩展名的前 10 位数字。确保电话号码符合 NANPA 的唯一方法是知道区号,所以我觉得必须有区号才能返回 true。见:rubular.com/r/xxoCmSft8H
  • 另外,format_pattern 不允许前导 1,但 nanpa 模式允许。
  • 另外,上面的 NANPA 模式有“(?”在它里面,这不是一个正确的正则表达式模式。我假设你的意思是“(?:”。顺便说一句,我一直放的唯一原因这些 cmets 是因为这是迄今为止在互联网上找到的最好的编译 :)。我正在尝试自己使用它并帮助其他谷歌用户。

标签: php regex validation phone-number


【解决方案1】:

当前的正则表达式

/^[\(]?(\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

?>

此修改提供了多项改进。

  1. 它会创建一组可配置的可与扩展名匹配的项目。您可以为扩展添加额外的分隔符。 这是最初的请求。该扩展还允许在扩展分隔符后使用冒号。
  2. 它将 4 个可选分隔符(点、空格、斜杠或连字符)的序列转换为仅匹配单个分隔符的字符类。
  3. 它对项目进行适当的分组。在给定的示例中,您可以在左括号之间没有区号,并且可以在没有扩展名的情况下使用扩展标记(空格-x)。此备用正则表达式需要完整的区号或不需要,并且需要完整的扩展名或不需要。
  4. 号码的 4 个组成部分(区号、中心办公室代码、电话号码和分机号)是回溯引用的元素,它们会在 preg_match() 中输入 $matches。
  5. 使用前瞻/后瞻来要求区号中的括号匹配。
  6. 允许在数字前使用 1-。 (这假设所有数字都是美国或加拿大的数字,这似乎是合理的,因为匹配最终是根据 NANPA 限制进行的。也不允许在括号中混合国家代码前缀和区号。
  7. 它合并到 NANPA 规则中以消除不可分配的电话号码。
    1. 它消除了 0xx、1xx、37x、96x、x9x 和 x11 形式的区号,这些区号是无效的 NANPA 区号。
    2. 它消除了 0xx 和 1xx 形式的中心办公室代码(无效的 NANPA 中心办公室代码)。
    3. 它消除了 555-01xx 形式的数字(不能从 NANPA 分配)。

它有一些小的限制。它们可能并不重要,但在此处已注明。

  1. 没有任何规定要求重复使用相同的分隔符,允许使用 800-555.1212、800/555 1212、800 555.1212 等数字。
  2. 没有任何地方可以限制带括号的区号后的分隔符,允许使用 (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)))$/

【讨论】:

  • 哇...我什至没有想过你指出的缺陷。非常感谢您让我知道!我也没有想过尝试针对两种不同的模式运行它,以便首先验证格式,然后验证实际数字......我会试一试,看看会发生什么。谢谢!
  • 嗯,当我尝试通过第一个模式运行测试号时,我得到这个:Warning: preg_match() [function.preg-match]: Unknown modifier '\'
  • 修复了一个可能解决该问题的错误,在字符类中转义 /。
  • 我使用了你的答案和 enobrev 的答案来形成完整的功能......我已经使用 enobrev 来检查格式本身,然后你的来验证数字字符串(经过清理后)针对NANPA 指南。谢谢!
【解决方案2】:

您可以使用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'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";
        }
    }
?>

还有输出:

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

【讨论】:

  • 我喜欢这个,因为它对我来说更容易理解......但是,正如 ebynum 在下面关于我的原始正则表达式模式指出的那样,当区号周围的括号时,这个也允许匹配不包含任何值... 像这样: () 456-7890 ext1234 = match 而它应该会失败,因为区号标识符已放置到位,但未填充。但是,我想就这个问题的“原始请求”而言,您的正则表达式模式几乎涵盖了该部分。
  • 我结合了你的答案和 ebynum 的答案来形成完整的功能...我用你的方法检查格式本身,然后 ebynum 验证数字字符串(经过消毒后)针对NANPA 指南。谢谢!
  • 这个正则表达式目前允许使用反斜杠作为分隔符,例如 (xxx)xxx\xxxx。它使用冗余前瞻(执行前瞻搜索以查看是否有扩展,然后需要扩展)。
  • @ebynum 你是对的。我的模式是基于 OP 的。不确定您对冗余前瞻的含义。它本质上是在寻找单词/缩写和数字,这是故意的,因为拥有像 xxx-xxx-xxxx-xxx 这样的电话号码似乎是不正确的。
  • 我不同意您的偏好(但这只是个人偏好)。我对 1-800-555-1212 1234 之类的数字完全没问题。但是,您的正则表达式仍然这样说: ... 允许一个单词/缩写作为扩展名,但前提是它后面跟着一个或多个数字,并且它后面必须跟一个或多个数字。如果你取出前瞻,留下:((?:(?:(?:e|x|ex|ext)\.?|extension)\s?)(\d+))?,它仍然必须同时拥有单词和数字(或什么都没有)。
【解决方案3】:

或者,您可以使用一些非常简单直接的 JavaScript 来强制用户以更指定的格式输入。用于 jQuery 的屏蔽输入插件 (http://digitalbush.com/projects/masked-input-plugin/) 允许您将 HTML 输入屏蔽为电话号码,只允许该人输入 xxx-xxx-xxxx 格式的数字。它不能解决您的扩展程序问题,但它确实提供了更清晰的用户体验。

【讨论】:

  • 注意:您仍然需要确认输入是您需要的形式(在我的示例中,xxx-xxx-xxxx)。否则,没有 JavaScript 的人(或正在规避它的人)仍然可以插入无效内容。
  • 我喜欢这个解决方案。类似地,您可以走一条技术含量更低的路线,只使用 3 或 4 个文本框:区号、号码(1 或 2 个框)、分机号。
【解决方案4】:

为什么不将任何一系列字母转换为“x”。那么这样你就可以将所有可能性转换为“x”。

检查 3digits、3digits、4digits、1orMoreDigits 并忽略其间的任何其他字符

正则表达式: ([0-9]{3}).*?([0-9]{3}).*?([0-9]{4}).+?([0-9]{1,})

【讨论】:

  • 而不是寻找 "[x]" 或 "x" 然后你只寻找数字 "[\w\s]*\d+" 。因此,如果他们使用电话号码 X 分机或电话号码 EXTENSION 分机或电话号码 EXT 分机,您不必担心。
  • @Josh - 类似于([0-9]{3}).*?([0-9]{3}).*?([0-9]{4}).+?([0-9]{1,}) 这将忽略分隔符并验证它们是否确实输入了有效数字
  • 这个表达式匹配了很多他不想匹配的东西,比如:我的地址是 123 Main Street, Cityville, ST。我真的很喜欢 PI。这是我最喜欢的号码。它是 3.1415926535。另外,我不确定。+?是一个有效的序列(相邻的重复量词),但如果你想要制作 .可选,一个简单的 .* 就足够了。
  • @ebynum - .+? 表示一个或多个.. 但匹配的最低数量(它是一个惰性量词)
  • 触摸。我总是忘记那些懒惰的量词。
【解决方案5】:

好吧,你可以修改正则表达式,但它不会很好——你应该允许“extn”吗? “程度”怎么样? “然后你必须拨号”怎么样?

我认为“正确”的做法是添加一个单独的数字扩展表单框。

但如果你真的想要正则表达式,我想我已经修好了。提示:单个字符不需要[x]x 就可以了。

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

您允许使用点、斜线、破折号、空格字符。您应该只允许这些选项之一。您需要更新对$matches 的引用;有用的组现在是 0、2 和 4。

附:这是未经测试的,因为我没有运行 PHP 的参考实现。如有错误请见谅,如有发现请告诉我,我会尽力改正。

编辑

这比我总结的要好得多here

【讨论】:

  • 哈哈,我明白你从哪里来...这可能会变成一场无休止的战斗...但是,我认为为了简单起见,我宁愿把电话号码全部输入一枪。我想我可以作为最后的手段终止扩展。
  • 目前的正则表达式允许xext。如果要添加其他选项,可以通过将(x|ext) 更改为(x|ext|spam|ham|eggs) 来添加它们。但是,我认为如果用户真的想要添加扩展程序,这将造成无穷无尽的混乱。
猜你喜欢
  • 1970-01-01
  • 2015-01-19
  • 2012-05-22
  • 2010-09-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-21
  • 2021-11-10
相关资源
最近更新 更多