preg_match(): 编译失败:字符类中的范围无效,偏移量为。

76

非常感谢您花时间帮助解决这个问题。

preg_match(): Compilation failed: invalid range in character class at offset 20 session.php on line 278

这段代码在我们服务器进行 PHP 升级后突然停止工作,而之前已经运行了数月时间。

以下是代码:

    else{
     /* Spruce up username, check length */
     $subuser = stripslashes($subuser);
     if(strlen($subuser) < $config['min_user_chars']){
        $form->setError($field, "* Username below ".$config['min_user_chars']."characters");
     }
     else if(strlen($subuser) > $config['max_user_chars']){
        $form->setError($field, "* Username above ".$config['max_user_chars']."characters");
     }


     /* Check if username is not alphanumeric */
    /* PREG_MATCH CODE */

     else if(!preg_match("/^[a-z0-9]([0-9a-z_-\s])+$/i", $subuser)){        
        $form->setError($field, "* Username not alphanumeric");
     }


    /* PREG_MATCH CODE */


     /* Check if username is reserved */
     else if(strcasecmp($subuser, GUEST_NAME) == 0){
        $form->setError($field, "* Username reserved word");
     }
     /* Check if username is already in use */
     else if($database->usernameTaken($subuser)){
        $form->setError($field, "* Username already in use");
     }
     /* Check if username is banned */
     else if($database->usernameBanned($subuser)){
        $form->setError($field, "* Username banned");
     }
  }
5个回答

136
问题非常古老,但是与PHP 7.3及更高版本有关的一些新进展需要介绍。PHP PCRE引擎迁移到PCRE2,而PHP 7.3中使用的PCRE库版本为10.32,这就是向后不兼容的更改的来源:
  • 内部库API已更改。
  • “S”修饰符无效,模式会自动进行研究。没有真正的影响。
  • “X”修饰符是PCRE2的默认行为。当前补丁将其行为恢复为PCRE中的“X”的含义,但最好采用新行为并默认打开“X”。因此目前也没有影响。
  • 由于较新的Unicode引擎,出现了一些行为变化。 PCRE2中的Unicode 10与PCRE中的Unicode 7有所不同,可能会在无效模式下发现某些行为变化。

根据PHP 10.33 changelog:

  1. 设置了PCRE2_EXTRA_BAD_ESCAPE_IS_LITERAL后,字符类中有效但不能作为范围结尾的转义序列(例如\s)将被视为文字。 例如[_-\s](但不是[\s-_],因为它在范围的开头会导致错误)。现在会给出“无效范围”错误。
在 PHP 7.3 以前,如果你转义连字符或将其放置在“不能被解释为表示范围”的位置,则可以在字符类中的任何位置使用它。在PHP 7.3中,似乎将PCRE2_EXTRA_BAD_ESCAPE_IS_LITERAL设置为false。因此,从现在开始,为了将连接符放入字符类中,请始终仅在开头或结尾处使用它
参见此引用
简而言之,PCRE2对模式验证更为严格,因此在升级后,您的某些现有模式可能无法编译。
如上面的示例中所示,两行代码之间存在微小但实质性的差异。

1
http://sandbox.onlinephpfunctions.com/code/7e98237a86c7c0822ce3fbd5323b3b849e43ae4e - Edmunds22
@Wiktor Stribizew,你能帮我解决为什么这个表达式在PHP 7.4.5中不起作用吗?它已经让我们无法将系统从PHP 5.6迁移到PHP 7.x。如果您能帮忙,我将不胜感激。:(/([\w-:*]*)(?:#([\w-]+)|.([\w-]+))?(?:[@?(!?[\w-:]+)(?:([!^$]?=)["']?(.?)["']?)?])?([/, ]+)/is - Saeed Afzal
我们最近从PHP 7.2升级到了PHP 7.3,遇到了类似的问题。很遗憾,在小版本的PHP升级中,存在着许多不向后兼容的问题... - ElLocoCocoLoco
太棒了,你为我使用 PHP 7.4 完成了它。 - saleh asadi
太棒了,你为我做了一个基于 PHP 7.4 的项目。 - saleh asadi
显示剩余2条评论

34

在字符类(正则表达式中的[])中,可以使用-来定义一个字符类范围。例如[0-9]表示0到9之间的所有字符。在你的正则表达式中,有几个字符类范围:a-z0-9。但是,你可能不是故意放了一个字符类_-\s

"/^[a-z0-9]([0-9a-z_-\s])+$/i"
                   ^^^^ 

显然在一些(甚至大多数?)版本的PCRE中,这不被视为无效的字符范围(PHP使用的正则表达式库),但最近可能已经改变了,如果服务器上升级了PCRE库,那可能就是原因。

Debuggex是一个不错的工具,可以帮助调试错误(PHP的错误消息已经告诉你错误所在的行和字符号..),像这样(我没有关联,只是一个粉丝)。


10
根据RegexBuddy的说法,PHP 5.5需要转义连字符或将其移至列表末尾,以使其匹配一个字面上的连字符,否则无法匹配。在此之前,显然是因为"_ -\s"没有意义作为一个范围,所以它只是认为你的意思就是那样。 - Alan Moore
@AlanMoore:一个鲜为人知的可能性是将连字符放在速记字符类\s-_之后。 - Casimir et Hippolyte
1
@AlanMoore:你也可以写成这样:[a-z-0-9][a-z-1]。然后在使用PCRE时,“规则”似乎是:在字符类中,需要转义连字符,除非它位于类的开头或否定符号后面,在范围、简写字符类之后,在结尾之后或在简写字符类之前和之后。换句话说,当情况不明确时,您不需要转义连字符,除了无效的范围。 - Casimir et Hippolyte
4
在生产服务器上发现了同样的问题...该服务器未更新到最新版本的PHP,代码像往常一样工作,但在测试服务器上出现了错误。在我的情况下,我需要保留空格[\s]的引用,所以我转义了连字符[-\s]解决了这个问题,并且也按预期工作。只是一个想法。 - raphie
1
我刚遇到了这个问题,即在速记字符类(\d-.)后面加上破折号,所以显然PHP在7.3.1版本中不再接受它。 - Brilliand
显示剩余5条评论

29

你的错误取决于你的正则表达式解释器。

你应该转义连字符以澄清它是一个字符。因此,使用\-代替-

你的最终代码:

/^[a-z0-9]([0-9a-z_\-\s])+$/i

7

也许这个答案可以帮助那些需要创建阿拉伯语/波斯语Slug的人:

对于php版本为7.3,请使用\-代替-

[^a-z0-9_\s-

以及

"/[\s-_]+/"

因此,针对php 7.3的阿拉伯文make_slug函数如下:

function make_slug($string, $separator = '-')
{
    $string = trim($string);
    $string = mb_strtolower($string, 'UTF-8');

    // Make alphanumeric (removes all other characters)
    // this makes the string safe especially when used as a part of a URL
    // this keeps latin characters and Persian characters as well
    $string = preg_replace("/[^a-z0-9_\s\-ءاآؤئبپتثجچحخدذرزژسشصضطظعغفقكکگلمنوهی]/u", '', $string);

    // Remove multiple dashes or whitespaces or underscores
    $string = preg_replace("/[\s\-_]+/", ' ', $string);

    // Convert whitespaces and underscore to the given separator
    $string = preg_replace("/[\s_]/", $separator, $string);

    return $string;
}

-3

我遇到了这个错误,通过以下方法解决:

Route::get('{path}','HomeController@index')->where( 'path', '([A-z]+)?' );

这对我起作用了。


3
"A-z" 匹配的不仅是字母,可以查看 ASCII 表格了解更多信息。 - Toto
认真地说,你是怎么想出这个的? - Murwa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接