【问题标题】:PHP validation/regex for URLURL 的 PHP 验证/正则表达式
【发布时间】:2010-09-17 09:21:49
【问题描述】:

我一直在寻找一个简单的 URL 正则表达式,有没有人有一个很好用的方便的?我没有找到一个带有 zend 框架验证类的类,并且已经看到了几个实现。

【问题讨论】:

标签: php regex url validation


【解决方案1】:

使用filter_var()函数验证字符串是否为URL:

var_dump(filter_var('example.com', FILTER_VALIDATE_URL));

在不必要的时候使用正则表达式是不好的做法。

编辑:小心,这个解决方案不是 unicode 安全的,也不是 XSS 安全的。如果您需要复杂的验证,也许最好去别处看看。

【讨论】:

  • 在 5.2.13(我认为是 5.3.2)中存在一个错误,它会阻止带有破折号的网址使用此方法进行验证。
  • filter_var 将拒绝test-site.com,我的域名带有破折号,无论它们是否有效。我不认为 filter_var 是验证 url 的最佳方式。它将允许像 http://www 这样的网址
  • > 它将允许像'www'这样的URL 像'localhost'这样的URL 就可以了
  • 这种方法的另一个问题是它不是 unicode 安全的。
  • FILTER_VALIDATE_URL 有 a lot of problems 需要修复。此外,docs describing the flags 不反映 actual source code 对某些标志的引用已被完全删除。更多信息在这里:news.php.net/php.internals/99018
【解决方案2】:

我在几个项目中使用了它,我相信我没有遇到问题,但我确信它并不详尽:

$text = preg_replace(
  '#((https?|ftp)://(\S*?\.\S*?))([\s)\[\]{},;"\':<]|\.\s|$)#i',
  "'<a href=\"$1\" target=\"_blank\">$3</a>$4'",
  $text
);

末尾的大部分随机垃圾都是为了处理句子中的http://domain.com.之类的情况(避免匹配尾随句点)。我确信它可以被清理,但因为它有效。我或多或少只是将它从一个项目复制到另一个项目。

【讨论】:

  • 一些让我大吃一惊的事情:在需要字符类的地方使用交替(每个选项都匹配一个字符);并且替换不应该需要外部双引号(它们只是因为正则表达式上无意义的 /e 修饰符才需要)。
  • @John Scipione:google.com 只是一个有效的相对 URL 路径,而不是一个有效的绝对 URL。我认为这就是他正在寻找的。​​span>
  • 这在这种情况下不起作用 - 它包括结尾的 ": 3 cantari noi in albumul audio.resursecrestine.ro/cantece/index-autori/andrei-rosu/…>
  • @Softy 类似http://example.com/somedir/... 是一个完全合法的 URL,要求提供名为 ... 的文件 - 这是一个合法的文件名。
  • 我正在使用 Zend\Validator\Regex 使用您的模式验证 url,但它仍然检测到 http://www.example 有效
【解决方案3】:

根据 PHP 手册 - parse_url 应该用于验证 URL。

不幸的是,filter_var('example.com', FILTER_VALIDATE_URL) 似乎并没有表现得更好。

parse_url()filter_var() 都会传递格式错误的 URL,例如 http://...

因此在这种情况下 - 正则表达式 更好的方法。

【讨论】:

  • 这个论点不成立。如果 FILTER_VALIDATE_URL 比您想要的稍微宽松一点,请添加一些额外的检查来处理这些边缘情况。通过您自己尝试针对 url 的正则表达式来重新发明*只会让您远离完整的检查。
  • 查看此页面上所有被击落的正则表达式,了解为什么 - 不 - 编写自己的示例。
  • 你说得对,查尔瓦克。诸如 URL 之类的正则表达式可能(根据其他响应)很难正确处理。正则表达式并不总是答案。相反,正则表达式也不总是错误的答案。重要的一点是为工作选择正确的工具(正则表达式或其他),而不是专门“反”或“专业”正则表达式。事后看来,您使用 filter_var 并结合其边缘情况的约束的答案看起来是更好的答案(特别是当正则表达式答案开始超过 100 个字符左右时 - 使所述正则表达式的维护成为一场噩梦)
【解决方案4】:

以防万一您想知道该 url 是否真的存在:

function url_exist($url){//se passar a URL existe
    $c=curl_init();
    curl_setopt($c,CURLOPT_URL,$url);
    curl_setopt($c,CURLOPT_HEADER,1);//get the header
    curl_setopt($c,CURLOPT_NOBODY,1);//and *only* get the header
    curl_setopt($c,CURLOPT_RETURNTRANSFER,1);//get the response as a string from curl_exec(), rather than echoing it
    curl_setopt($c,CURLOPT_FRESH_CONNECT,1);//don't use a cached version of the url
    if(!curl_exec($c)){
        //echo $url.' inexists';
        return false;
    }else{
        //echo $url.' exists';
        return true;
    }
    //$httpcode=curl_getinfo($c,CURLINFO_HTTP_CODE);
    //return ($httpcode<400);
}

【讨论】:

  • 在实际验证 url 是否真实之前,我仍然会在 $url 上进行某种验证,因为上述操作很昂贵 - 可能长达 200 毫秒,具体取决于文件大小。在某些情况下,url 可能实际上在其位置上还没有可用的资源(例如,为尚未上传的图像创建 url)。此外,您没有使用缓存版本,因此它不像 file_exists() 那样会缓存文件上的统计信息并几乎立即返回。您提供的解决方案仍然有用。为什么不直接使用fopen($url, 'r')
  • 谢谢,正是我想要的。但是,我在尝试使用它时犯了一个错误。该函数是“url_exist”而不是“url_exists”哎呀;-)
  • 直接访问用户输入的网址有安全隐患吗?
  • 您想添加检查是否找到 404: $httpCode = curl_getinfo( $c, CURLINFO_HTTP_CODE ); //回显 $url . ' ' 。 $http代码。 '<br>'; if( $httpCode == 404 ) { echo $url.' 404'; }
  • 根本不安全。任何输入的 URL 都会被主动访问。
【解决方案5】:

根据 约翰·格鲁伯(大胆的火球):

正则表达式:

(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'\".,<>?«»“”‘’]))

在 preg_match() 中使用:

preg_match("/(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'\".,<>?«»“”‘’]))/", $url)

这是扩展的正则表达式模式(使用 cmets):

(?xi)
\b
(                       # Capture 1: entire matched URL
  (?:
    https?://               # http or https protocol
    |                       #   or
    www\d{0,3}[.]           # "www.", "www1.", "www2." … "www999."
    |                           #   or
    [a-z0-9.\-]+[.][a-z]{2,4}/  # looks like domain name followed by a slash
  )
  (?:                       # One or more:
    [^\s()<>]+                  # Run of non-space, non-()<>
    |                           #   or
    \(([^\s()<>]+|(\([^\s()<>]+\)))*\)  # balanced parens, up to 2 levels
  )+
  (?:                       # End with:
    \(([^\s()<>]+|(\([^\s()<>]+\)))*\)  # balanced parens, up to 2 levels
    |                               #   or
    [^\s`!()\[\]{};:'".,<>?«»“”‘’]        # not a space or one of these punct chars
  )
)

更多详情请看: http://daringfireball.net/2010/07/improved_regex_for_matching_urls

【讨论】:

  • 为了工作,该模式需要在三点用反斜杠转义正斜杠: preg_match("/(?i)\b((?:https?:\/\/|www\d {0,3}[.]|[a-z0-9.\-]+[.][az]{2,4}\/)(?:[^\s()]+|(( [^\s()]+|(([^\s()]+)))*))+(?:(([^\s()]+|(([^ \s()]+)))*)|[^\s`!()[]{};:'\".,?«»“”'']))/", $url )
【解决方案6】:

我不认为在这种情况下使用正则表达式是一件明智的事情。不可能匹配所有的可能性,即使你做到了,仍然有可能 url 根本不存在。

这是一个非常简单的方法来测试 url 是否真的存在并且是否可读:

if (preg_match("#^https?://.+#", $link) and @fopen($link,"r")) echo "OK";

(如果没有preg_match,那么这也会验证您服务器上的所有文件名)

【讨论】:

    【解决方案7】:

    我用这个很成功——我不记得我是从哪里弄来的

    $pattern = "/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i";
    

    【讨论】:

    • ^(http://|https://)?(([a-z0-9]?([-a-z0-9]*[a-z0-9]+) ?){1,63}\.)+[az]{2,6}(可能太贪心了,目前还不确定,但在协议和领先的www上更灵活)
    【解决方案8】:
        function validateURL($URL) {
          $pattern_1 = "/^(http|https|ftp):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+.(com|org|net|dk|at|us|tv|info|uk|co.uk|biz|se)$)(:(\d+))?\/?/i";
          $pattern_2 = "/^(www)((\.[A-Z0-9][A-Z0-9_-]*)+.(com|org|net|dk|at|us|tv|info|uk|co.uk|biz|se)$)(:(\d+))?\/?/i";       
          if(preg_match($pattern_1, $URL) || preg_match($pattern_2, $URL)){
            return true;
          } else{
            return false;
          }
        }
    

    【讨论】:

    • 不适用于以下链接:'www.w3schools.com/home/3/?a=l'
    【解决方案9】:

    最适合我的 URL 正则表达式:

    function valid_URL($url){
        return preg_match('%^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@|\d{1,3}(?:\.\d{1,3}){3}|(?:(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+)(?:\.(?:[a-z\d\x{00a1}-\x{ffff}]+-?)*[a-z\d\x{00a1}-\x{ffff}]+)*(?:\.[a-z\x{00a1}-\x{ffff}]{2,6}))(?::\d+)?(?:[^\s]*)?$%iu', $url);
    }
    

    例子:

    valid_URL('https://twitter.com'); // true
    valid_URL('http://twitter.com');  // true
    valid_URL('http://twitter.co');   // true
    valid_URL('http://t.co');         // true
    valid_URL('http://twitter.c');    // false
    valid_URL('htt://twitter.com');   // false
    
    valid_URL('http://example.com/?a=1&b=2&c=3'); // true
    valid_URL('http://127.0.0.1');    // true
    valid_URL('');                    // false
    valid_URL(1);                     // false
    

    来源:http://urlregex.com/

    【讨论】:

      【解决方案10】:

      编辑:
      正如incidence 指出的那样,此代码已随着 PHP 5.3.0 (2009-06-30) 的发布而被弃用,应相应地使用。


      只有我的两分钱,但我已经开发了这个功能,并且已经成功使用了一段时间。它有很好的文档记录和分离,因此您可以轻松更改它。

      // Checks if string is a URL
      // @param string $url
      // @return bool
      function isURL($url = NULL) {
          if($url==NULL) return false;
      
          $protocol = '(http://|https://)';
          $allowed = '([a-z0-9]([-a-z0-9]*[a-z0-9]+)?)';
      
          $regex = "^". $protocol . // must include the protocol
                   '(' . $allowed . '{1,63}\.)+'. // 1 or several sub domains with a max of 63 chars
                   '[a-z]' . '{2,6}'; // followed by a TLD
          if(eregi($regex, $url)==true) return true;
          else return false;
      }
      

      【讨论】:

      • Eregi 将在 PHP 6.0.0 中被移除。带有“öäåø”的域将无法通过您的功能进行验证。您可能应该先将 URL 转换为 punycode?​​span>
      • @incidence 绝对同意。我在 3 月写了这篇文章,PHP 5.3 仅在 6 月下旬发布,将 eregi 设置为已弃用。谢谢你。将编辑和更新。
      • 如果我错了,请纠正我,但我们仍然可以假设 TLD 至少有 2 个字符,最多有 6 个字符吗?
      • @YzmirRamirez(这么多年过去了……)如果您在写评论时有任何疑问,现在肯定不会有任何疑问,现在使用 .photography 等*域名
      • @NickRice 你是对的......网络在 5 年内发生了多少变化。现在我迫不及待地等到有人制作 TLD .supercalifragilisticexpialidocious
      【解决方案11】:

      这就是你的答案 =) 试着打破它,你不能!!!

      function link_validate_url($text) {
      $LINK_DOMAINS = 'aero|arpa|asia|biz|com|cat|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|mobi|local';
        $LINK_ICHARS_DOMAIN = (string) html_entity_decode(implode("", array( // @TODO completing letters ...
          "&#x00E6;", // æ
          "&#x00C6;", // Æ
          "&#x00C0;", // À
          "&#x00E0;", // à
          "&#x00C1;", // Á
          "&#x00E1;", // á
          "&#x00C2;", // Â
          "&#x00E2;", // â
          "&#x00E5;", // å
          "&#x00C5;", // Å
          "&#x00E4;", // ä
          "&#x00C4;", // Ä
          "&#x00C7;", // Ç
          "&#x00E7;", // ç
          "&#x00D0;", // Ð
          "&#x00F0;", // ð
          "&#x00C8;", // È
          "&#x00E8;", // è
          "&#x00C9;", // É
          "&#x00E9;", // é
          "&#x00CA;", // Ê
          "&#x00EA;", // ê
          "&#x00CB;", // Ë
          "&#x00EB;", // ë
          "&#x00CE;", // Î
          "&#x00EE;", // î
          "&#x00CF;", // Ï
          "&#x00EF;", // ï
          "&#x00F8;", // ø
          "&#x00D8;", // Ø
          "&#x00F6;", // ö
          "&#x00D6;", // Ö
          "&#x00D4;", // Ô
          "&#x00F4;", // ô
          "&#x00D5;", // Õ
          "&#x00F5;", // õ
          "&#x0152;", // Œ
          "&#x0153;", // œ
          "&#x00FC;", // ü
          "&#x00DC;", // Ü
          "&#x00D9;", // Ù
          "&#x00F9;", // ù
          "&#x00DB;", // Û
          "&#x00FB;", // û
          "&#x0178;", // Ÿ
          "&#x00FF;", // ÿ 
          "&#x00D1;", // Ñ
          "&#x00F1;", // ñ
          "&#x00FE;", // þ
          "&#x00DE;", // Þ
          "&#x00FD;", // ý
          "&#x00DD;", // Ý
          "&#x00BF;", // ¿
        )), ENT_QUOTES, 'UTF-8');
      
        $LINK_ICHARS = $LINK_ICHARS_DOMAIN . (string) html_entity_decode(implode("", array(
          "&#x00DF;", // ß
        )), ENT_QUOTES, 'UTF-8');
        $allowed_protocols = array('http', 'https', 'ftp', 'news', 'nntp', 'telnet', 'mailto', 'irc', 'ssh', 'sftp', 'webcal');
      
        // Starting a parenthesis group with (?: means that it is grouped, but is not captured
        $protocol = '((?:'. implode("|", $allowed_protocols) .'):\/\/)';
        $authentication = "(?:(?:(?:[\w\.\-\+!$&'\(\)*\+,;=" . $LINK_ICHARS . "]|%[0-9a-f]{2})+(?::(?:[\w". $LINK_ICHARS ."\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})*)?)?@)";
        $domain = '(?:(?:[a-z0-9' . $LINK_ICHARS_DOMAIN . ']([a-z0-9'. $LINK_ICHARS_DOMAIN . '\-_\[\]])*)(\.(([a-z0-9' . $LINK_ICHARS_DOMAIN . '\-_\[\]])+\.)*('. $LINK_DOMAINS .'|[a-z]{2}))?)';
        $ipv4 = '(?:[0-9]{1,3}(\.[0-9]{1,3}){3})';
        $ipv6 = '(?:[0-9a-fA-F]{1,4}(\:[0-9a-fA-F]{1,4}){7})';
        $port = '(?::([0-9]{1,5}))';
      
        // Pattern specific to external links.
        $external_pattern = '/^'. $protocol .'?'. $authentication .'?('. $domain .'|'. $ipv4 .'|'. $ipv6 .' |localhost)'. $port .'?';
      
        // Pattern specific to internal links.
        $internal_pattern = "/^(?:[a-z0-9". $LINK_ICHARS ."_\-+\[\]]+)";
        $internal_pattern_file = "/^(?:[a-z0-9". $LINK_ICHARS ."_\-+\[\]\.]+)$/i";
      
        $directories = "(?:\/[a-z0-9". $LINK_ICHARS ."_\-\.~+%=&,$'#!():;*@\[\]]*)*";
        // Yes, four backslashes == a single backslash.
        $query = "(?:\/?\?([?a-z0-9". $LINK_ICHARS ."+_|\-\.~\/\\\\%=&,$'():;*@\[\]{} ]*))";
        $anchor = "(?:#[a-z0-9". $LINK_ICHARS ."_\-\.~+%=&,$'():;*@\[\]\/\?]*)";
      
        // The rest of the path for a standard URL.
        $end = $directories .'?'. $query .'?'. $anchor .'?'.'$/i';
      
        $message_id = '[^@].*@'. $domain;
        $newsgroup_name = '(?:[0-9a-z+-]*\.)*[0-9a-z+-]*';
        $news_pattern = '/^news:('. $newsgroup_name .'|'. $message_id .')$/i';
      
        $user = '[a-zA-Z0-9'. $LINK_ICHARS .'_\-\.\+\^!#\$%&*+\/\=\?\`\|\{\}~\'\[\]]+';
        $email_pattern = '/^mailto:'. $user .'@'.'(?:'. $domain .'|'. $ipv4 .'|'. $ipv6 .'|localhost)'. $query .'?$/';
      
        if (strpos($text, '<front>') === 0) {
          return false;
        }
        if (in_array('mailto', $allowed_protocols) && preg_match($email_pattern, $text)) {
          return false;
        }
        if (in_array('news', $allowed_protocols) && preg_match($news_pattern, $text)) {
          return false;
        }
        if (preg_match($internal_pattern . $end, $text)) {
          return false;
        }
        if (preg_match($external_pattern . $end, $text)) {
          return false;
        }
        if (preg_match($internal_pattern_file, $text)) {
          return false;
        }
      
        return true;
      }
      

      【讨论】:

      • 还有很多top level domains
      • 你的.?+^{}=|$,和@32tick @ 不需要在你的字符类中转义。 + 甚至在您的一个角色类别中重复出现。 : 不需要转义。
      【解决方案12】:
      function is_valid_url ($url="") {
      
              if ($url=="") {
                  $url=$this->url;
              }
      
              $url = @parse_url($url);
      
              if ( ! $url) {
      
      
                  return false;
              }
      
              $url = array_map('trim', $url);
              $url['port'] = (!isset($url['port'])) ? 80 : (int)$url['port'];
              $path = (isset($url['path'])) ? $url['path'] : '';
      
              if ($path == '') {
                  $path = '/';
              }
      
              $path .= ( isset ( $url['query'] ) ) ? "?$url[query]" : '';
      
      
      
              if ( isset ( $url['host'] ) AND $url['host'] != gethostbyname ( $url['host'] ) ) {
                  if ( PHP_VERSION >= 5 ) {
                      $headers = get_headers("$url[scheme]://$url[host]:$url[port]$path");
                  }
                  else {
                      $fp = fsockopen($url['host'], $url['port'], $errno, $errstr, 30);
      
                      if ( ! $fp ) {
                          return false;
                      }
                      fputs($fp, "HEAD $path HTTP/1.1\r\nHost: $url[host]\r\n\r\n");
                      $headers = fread ( $fp, 128 );
                      fclose ( $fp );
                  }
                  $headers = ( is_array ( $headers ) ) ? implode ( "\n", $headers ) : $headers;
                  return ( bool ) preg_match ( '#^HTTP/.*\s+[(200|301|302)]+\s#i', $headers );
              }
      
              return false;
          }
      

      【讨论】:

      • 您好,这个解决方案很好,我赞成,但它没有考虑到 https 的标准端口:--建议您只需将 80 替换为 '' 即可解决端口
      • 我最终实现了一个变体,因为我的域关心 URL 是否实际存在:)
      【解决方案13】:

      启发 in this .NET * questionin this referenced article from that question 有这个 URI 验证器(URI 表示它同时验证 URL 和 URN)。

      if( ! preg_match( "/^([a-z][a-z0-9+.-]*):(?:\\/\\/((?:(?=((?:[a-z0-9-._~!$&'()*+,;=:]|%[0-9A-F]{2})*))(\\3)@)?(?=(\\[[0-9A-F:.]{2,}\\]|(?:[a-z0-9-._~!$&'()*+,;=]|%[0-9A-F]{2})*))\\5(?::(?=(\\d*))\\6)?)(\\/(?=((?:[a-z0-9-._~!$&'()*+,;=:@\\/]|%[0-9A-F]{2})*))\\8)?|(\\/?(?!\\/)(?=((?:[a-z0-9-._~!$&'()*+,;=:@\\/]|%[0-9A-F]{2})*))\\10)?)(?:\\?(?=((?:[a-z0-9-._~!$&'()*+,;=:@\\/?]|%[0-9A-F]{2})*))\\11)?(?:#(?=((?:[a-z0-9-._~!$&'()*+,;=:@\\/?]|%[0-9A-F]{2})*))\\12)?$/i", $uri ) )
      {
          throw new \RuntimeException( "URI has not a valid format." );
      }
      

      我已经成功地在一个名为 Uri 并由 UriTest 测试的 ValueObject 中对这个函数进行了单元测试。

      UriTest.php(包含 URL 和 URN 的有效和无效案例)

      <?php
      
      declare( strict_types = 1 );
      
      namespace XaviMontero\ThrasherPortage\Tests\Tour;
      
      use XaviMontero\ThrasherPortage\Tour\Uri;
      
      class UriTest extends \PHPUnit_Framework_TestCase
      {
          private $sut;
      
          public function testCreationIsOfProperClassWhenUriIsValid()
          {
              $sut = new Uri( 'http://example.com' );
              $this->assertInstanceOf( 'XaviMontero\\ThrasherPortage\\Tour\\Uri', $sut );
          }
      
          /**
           * @dataProvider urlIsValidProvider
           * @dataProvider urnIsValidProvider
           */
          public function testGetUriAsStringWhenUriIsValid( string $uri )
          {
              $sut = new Uri( $uri );
              $actual = $sut->getUriAsString();
      
              $this->assertInternalType( 'string', $actual );
              $this->assertEquals( $uri, $actual );
          }
      
          public function urlIsValidProvider()
          {
              return
                  [
                      [ 'http://example-server' ],
                      [ 'http://example.com' ],
                      [ 'http://example.com/' ],
                      [ 'http://subdomain.example.com/path/?parameter1=value1&parameter2=value2' ],
                      [ 'random-protocol://example.com' ],
                      [ 'http://example.com:80' ],
                      [ 'http://example.com?no-path-separator' ],
                      [ 'http://example.com/pa%20th/' ],
                      [ 'ftp://example.org/resource.txt' ],
                      [ 'file://../../../relative/path/needs/protocol/resource.txt' ],
                      [ 'http://example.com/#one-fragment' ],
                      [ 'http://example.edu:8080#one-fragment' ],
                  ];
          }
      
          public function urnIsValidProvider()
          {
              return
                  [
                      [ 'urn:isbn:0-486-27557-4' ],
                      [ 'urn:example:mammal:monotreme:echidna' ],
                      [ 'urn:mpeg:mpeg7:schema:2001' ],
                      [ 'urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66' ],
                      [ 'rare-urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66' ],
                      [ 'urn:FOO:a123,456' ]
                  ];
          }
      
          /**
           * @dataProvider urlIsNotValidProvider
           * @dataProvider urnIsNotValidProvider
           */
          public function testCreationThrowsExceptionWhenUriIsNotValid( string $uri )
          {
              $this->expectException( 'RuntimeException' );
              $this->sut = new Uri( $uri );
          }
      
          public function urlIsNotValidProvider()
          {
              return
                  [
                      [ 'only-text' ],
                      [ 'http//missing.colon.example.com/path/?parameter1=value1&parameter2=value2' ],
                      [ 'missing.protocol.example.com/path/' ],
                      [ 'http://example.com\\bad-separator' ],
                      [ 'http://example.com|bad-separator' ],
                      [ 'ht tp://example.com' ],
                      [ 'http://exampl e.com' ],
                      [ 'http://example.com/pa th/' ],
                      [ '../../../relative/path/needs/protocol/resource.txt' ],
                      [ 'http://example.com/#two-fragments#not-allowed' ],
                      [ 'http://example.edu:portMustBeANumber#one-fragment' ],
                  ];
          }
      
          public function urnIsNotValidProvider()
          {
              return
                  [
                      [ 'urn:mpeg:mpeg7:sch ema:2001' ],
                      [ 'urn|mpeg:mpeg7:schema:2001' ],
                      [ 'urn?mpeg:mpeg7:schema:2001' ],
                      [ 'urn%mpeg:mpeg7:schema:2001' ],
                      [ 'urn#mpeg:mpeg7:schema:2001' ],
                  ];
          }
      }
      

      Uri.php(值对象)

      <?php
      
      declare( strict_types = 1 );
      
      namespace XaviMontero\ThrasherPortage\Tour;
      
      class Uri
      {
          /** @var string */
          private $uri;
      
          public function __construct( string $uri )
          {
              $this->assertUriIsCorrect( $uri );
              $this->uri = $uri;
          }
      
          public function getUriAsString()
          {
              return $this->uri;
          }
      
          private function assertUriIsCorrect( string $uri )
          {
              // https://*.com/questions/30847/regex-to-validate-uris
              // http://snipplr.com/view/6889/regular-expressions-for-uri-validationparsing/
      
              if( ! preg_match( "/^([a-z][a-z0-9+.-]*):(?:\\/\\/((?:(?=((?:[a-z0-9-._~!$&'()*+,;=:]|%[0-9A-F]{2})*))(\\3)@)?(?=(\\[[0-9A-F:.]{2,}\\]|(?:[a-z0-9-._~!$&'()*+,;=]|%[0-9A-F]{2})*))\\5(?::(?=(\\d*))\\6)?)(\\/(?=((?:[a-z0-9-._~!$&'()*+,;=:@\\/]|%[0-9A-F]{2})*))\\8)?|(\\/?(?!\\/)(?=((?:[a-z0-9-._~!$&'()*+,;=:@\\/]|%[0-9A-F]{2})*))\\10)?)(?:\\?(?=((?:[a-z0-9-._~!$&'()*+,;=:@\\/?]|%[0-9A-F]{2})*))\\11)?(?:#(?=((?:[a-z0-9-._~!$&'()*+,;=:@\\/?]|%[0-9A-F]{2})*))\\12)?$/i", $uri ) )
              {
                  throw new \RuntimeException( "URI has not a valid format." );
              }
          }
      }
      

      运行单元测试

      46 个测试中有 65 个断言。 注意:有 2 个数据提供者用于有效表达式,另外 2 个用于无效表达式。一个用于 URL,另一个用于 URN。如果您使用的是 v5.6* 或更早版本的 PhpUnit,则需要将两个数据提供者合并为一个。

      xavi@bromo:~/custom_www/hello-trip/mutant-migrant$ vendor/bin/phpunit
      PHPUnit 5.7.3 by Sebastian Bergmann and contributors.
      
      ..............................................                    46 / 46 (100%)
      
      Time: 82 ms, Memory: 4.00MB
      
      OK (46 tests, 65 assertions)
      

      代码覆盖率

      在这个示例 URI 检查器中有 100% 的代码覆盖率。

      【讨论】:

        【解决方案14】:
        "/(http(s?):\/\/)([a-z0-9\-]+\.)+[a-z]{2,4}(\.[a-z]{2,4})*(\/[^ ]+)*/i"
        
        1. (http(s?)://) 表示 http:// 或 https://

        2. ([a-z0-9-]+.)+ => 2.0[a-z0-9-] 表示任何 a-z 字符或任何 0-9 或 (-) 符号)

                       2.1 (+) means the character can be one or more ex: a1w, 
                           a9-,c559s, f)
          
                       2.2 \. is (.)sign
          
                       2.3. the (+) sign after ([a-z0-9\-]+\.) mean do 2.1,2.2,2.3 
                          at least 1 time 
                        ex: abc.defgh0.ig, aa.b.ced.f.gh. also in case www.yyy.com
          
                       3.[a-z]{2,4} mean a-z at least 2 character but not more than 
                                    4 characters for check that there will not be 
                                    the case 
                                    ex: https://www.google.co.kr.asdsdagfsdfsf
          
                       4.(\.[a-z]{2,4})*(\/[^ ]+)* mean 
          
                         4.1 \.[a-z]{2,4} means like number 3 but start with 
                             (.)sign 
          
                         4.2 * means (\.[a-z]{2,4})can be use or not use never mind
          
                         4.3 \/ means \
                         4.4 [^ ] means any character except blank
                         4.5 (+) means do 4.3,4.4,4.5 at least 1 times
                         4.6 (*) after (\/[^ ]+) mean use 4.3 - 4.5 or not use 
                             no problem
          
                         use for case https://*.com/posts/51441301/edit
          
                         5. when you use regex write in "/ /" so it come
          

          "/(http(s?)://)([a-z0-9-]+.)+[az]{2,4}(.[az]{2,4}) (/[^ ]+)/i"

                         6. almost forgot: letter i on the back mean ignore case of 
                            Big letter or small letter ex: A same as a, SoRRy same 
                            as sorry.
          

        注意:抱歉英语不好。我的国家用得不好。

        【讨论】:

        • 你注意到这个问题有多老了吗?请解释你的正则表达式,不知道的用户如果没有细节将很难理解。
        【解决方案15】:

        好的,所以这比简单的正则表达式要复杂一些,但它允许不同类型的 url。

        例子:

        所有应该被标记为有效的。

        function is_valid_url($url) {
            // First check: is the url just a domain name? (allow a slash at the end)
            $_domain_regex = "|^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(\.[A-Za-z]{2,})/?$|";
            if (preg_match($_domain_regex, $url)) {
                return true;
            }
        
            // Second: Check if it's a url with a scheme and all
            $_regex = '#^([a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))$#';
            if (preg_match($_regex, $url, $matches)) {
                // pull out the domain name, and make sure that the domain is valid.
                $_parts = parse_url($url);
                if (!in_array($_parts['scheme'], array( 'http', 'https' )))
                    return false;
        
                // Check the domain using the regex, stops domains like "-example.com" passing through
                if (!preg_match($_domain_regex, $_parts['host']))
                    return false;
        
                // This domain looks pretty valid. Only way to check it now is to download it!
                return true;
            }
        
            return false;
        }
        

        请注意,您要允许的协议有一个 in_array 检查(目前该列表中只有 http 和 https)。

        var_dump(is_valid_url('google.com'));         // true
        var_dump(is_valid_url('google.com/'));        // true
        var_dump(is_valid_url('http://google.com'));  // true
        var_dump(is_valid_url('http://google.com/')); // true
        var_dump(is_valid_url('https://google.com')); // true
        

        【讨论】:

        • 抛出:ErrorException:未定义的索引:如果协议未指定,我建议检查是否已设置。
        • @user3396065,你能提供一个抛出这个的示例输入吗?
        【解决方案16】:

        对于使用 WordPress 开发的任何人,只需使用

        esc_url_raw($url) === $url
        

        验证 URL (here's WordPress' documentation on esc_url_raw)。它处理 URL 比 filter_var($url, FILTER_VALIDATE_URL) 好得多,因为它 unicode 和 XSS 安全的。 (Here is a good article mentioning all the problems with filter_var)。

        【讨论】:

          【解决方案17】:

          Peter 的正则表达式在我看来不正确,原因有很多。它允许域名中的各种特殊字符,并且不需要太多测试。

          Frankie 的函数对我来说看起来不错,如果您不想要函数,可以从组件构建一个好的正则表达式,如下所示:

          ^(http://|https://)(([a-z0-9]([-a-z0-9]*[a-z0-9]+)?){1,63}\.)+[a-z]{2,6}
          

          未经测试,但我认为应该可以。

          另外,欧文的回答看起来也不是 100%。我取了正则表达式的域部分,并在正则表达式测试工具http://erik.eae.net/playground/regexp/regexp.html 上对其进行了测试

          我把下面这行:

          (\S*?\.\S*?)
          

          在“正则表达式”部分 和以下行:

          -hello.com

          在“示例文本”部分下。

          结果允许减号通过。因为 \S 表示任何非空格字符。

          注意 Frankie 的正则表达式处理减号,因为它的第一个字符有这个部分:

          [a-z0-9]
          

          不允许使用减号或任何其他特殊字符。

          【讨论】:

            【解决方案18】:

            这是我的做法。但我想提醒一下,我对正则表达式并不那么放心。但它应该对你有用:)

            $pattern = "#((http|https)://(\S*?\.\S*?))(\s|\;|\)|\]|\[|\{|\}|,|”|\"|'|:|\<|$|\.\s)#i";
                    $text = preg_replace_callback($pattern,function($m){
                            return "<a href=\"$m[1]\" target=\"_blank\">$m[1]</a>$m[4]";
                        },
                        $text);
            

            这样你就不需要在你的模式上使用 eval 标记了。

            希望对你有帮助:)

            【讨论】:

            • (http|https) 更简单的是https?。在这种模式中过度使用管道会对可读性和简洁性产生负面影响。模式中的许多转义字符不需要转义。
            【解决方案19】:

            这是一个使用 RegEx 的 URL Validation 简单类,然后将域与流行的 RBL(实时黑洞列表)服务器交叉引用:

            安装:

            require 'URLValidation.php';
            

            用法:

            require 'URLValidation.php';
            $urlVal = new UrlValidation(); //Create Object Instance
            

            添加一个 URL 作为domain() 方法的参数并检查返回。

            $urlArray = ['http://www.bokranzr.com/test.php?test=foo&test=dfdf', 'https://en-gb.facebook.com', 'https://www.google.com'];
            foreach ($urlArray as $k=>$v) {
            
                echo var_dump($urlVal->domain($v)) . ' URL: ' . $v . '<br>';
            
            }
            

            输出:

            bool(false) URL: http://www.bokranzr.com/test.php?test=foo&test=dfdf
            bool(true) URL: https://en-gb.facebook.com
            bool(true) URL: https://www.google.com
            

            正如您在上面看到的,www.bokranzr.com 通过 RBL 被列为恶意网站,因此该域被返回为 false。

            【讨论】:

              【解决方案20】:

              我发现这对于匹配 URL 最有用..

              ^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$
              

              【讨论】:

              • 会匹配以ftp: 开头的网址吗?
              • /^(https?:\/\/)?([\da-z\.-]+)\.([az\.]{2,6})([\/ \w \.-]*)*\/?$/
              【解决方案21】:

              有一个 PHP 原生函数可以做到这一点:

              $url = 'http://www.yoururl.co.uk/sub1/sub2/?param=1&param2/';
              
              if ( ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
                  // Wrong
              }
              else {
                  // Valid
              }
              

              返回过滤后的数据,如果过滤失败则返回 FALSE。

              Check it here

              【讨论】:

              • 这个答案重复了 2008 年的一个答案!