字符串损坏或preg_match错误


String corrupted or preg_match bug?

NO-BREAK 空格和许多其他 UTF-8 符号需要 2 个字节来表示;因此,在假定的 UTF8 字符串上下文中,非 ASCII (>127( 的隔离(前面没有 xC2(字节是一个无法识别的字符......好的,这只是一个布局问题(!(,但它破坏了整个字符串?

如何避免这种"非预期行为"?(它发生在某些函数中,而不是在其他函数中(。

示例(仅使用preg_match生成非预期行为(:

  header("Content-Type: text/plain; charset=utf-8"); // same if text/html
  //PHP Version 5.5.4-1+debphp.org~precise+1
  //using a .php file enconded as UTF8.
  $s = "THE UTF-8 NO-BREAK'xA0SPACE"; // a non-ASCII byte
  preg_match_all('/[-'''p{L}]+/u',$s,$m);
  var_dump($m);            // empty! (corrupted)
  $m=str_word_count($s,1);
  var_dump($m);            // ok
  $s = "THE UTF-8 NO-BREAK'xC2'xA0SPACE";  // utf8-encoded nbsp
  preg_match_all('/[-'''p{L}]+/u',$s,$m);
  var_dump($m);            // ok!
  $m=str_word_count($s,1);
  var_dump($m);            // ok
这不是

一个完整的答案,因为我没有说为什么一些PHP函数"在无效编码的字符串上完全失败"而其他函数则没有:请参阅@deceze at Question的评论和@hakre答案。如果您正在寻找 str_word_count() 的 PCRE 替代品,请参阅下面的preg_word_count()

PS:关于"PHP5 的内置库行为一致性"的讨论,我的结论是 PHP5 并没有那么糟糕,但我们创建了很多用户定义的包装(外观(函数(参见 PHP-framworks 的多样性!...或者等待PHP6 :-(


谢谢@pebbl!如果我理解您的链接,则 PHP 上缺少错误消息。因此,我的图示问题的一个可能的解决方法是添加一个错误条件......我在这里找到了条件(它确保有效的 utf8!...并感谢@deceze记住存在一个内置函数来检查此条件(之后我编辑了代码(。

问题放在一起,将解决方案转换为功能(已编辑,感谢@hakre评论!

 function my_word_count($s,$triggError=true) {
   if ( preg_match_all('/[-'''p{L}]+/u',$s,$m) !== false )
      return count($m[0]);
   else {
      if ($triggError) trigger_error(
         // not need mb_check_encoding($s,'UTF-8'), see hakre's answer, 
         // so, I wrong, there are no 'misteious error' with preg functions
         (preg_last_error()==PREG_BAD_UTF8_ERROR)? 
              'non-UTF8 input!': 'other error',
         E_USER_NOTICE
         );
      return NULL;
   }
 }

现在(在思考@hakre答案后编辑(,关于统一行为:我们可以开发一个合理的函数,使用PCRE库模仿str_word_count行为,接受错误的UTF8。对于此任务,我使用了@bobince iconv提示:

 /**
  * Like str_word_count() but showing how preg can do the same.
  * This function is most flexible but not faster than str_word_count.
  * @param $wRgx the "word regular expression" as defined by user.
  * @param $triggError changes behaviour causing error event.
  * @param $OnBadUtfTryAgain mimic the str_word_count behaviour.
  * @return 0 or positive integer as word-count, negative as PCRE error.
  */
 function preg_word_count($s,$wRgx='/[-'''p{L}]+/u', $triggError=true,
                          $OnBadUtfTryAgain=true) {
   if ( preg_match_all($wRgx,$s,$m) !== false )
      return count($m[0]);
   else {
      $lastError = preg_last_error();
      $chkUtf8 = ($lastError==PREG_BAD_UTF8_ERROR);
      if ($OnBadUtfTryAgain && $chkUtf8) 
         return preg_word_count(
            iconv('CP1252','UTF-8',$s), $wRgx, $triggError, false
         );
      elseif ($triggError) trigger_error(
         $chkUtf8? 'non-UTF8 input!': "error PCRE_code-$lastError",
         E_USER_NOTICE
         );
      return -$lastError;
   }
 }

演示(尝试其他输入!

 $s = "THE UTF-8 NO-BREAK'xA0SPACE"; // a non-ASCII byte
 print "'n-- str_word_count=".str_word_count($s,0);
 print "'n-- preg_word_count=".preg_word_count($s);
 $s = "THE UTF-8 NO-BREAK'xC2'xA0SPACE";  // utf8-encoded nbsp
 print "'n-- str_word_count=".str_word_count($s,0);
 print "'n-- preg_word_count=".preg_word_count($s);

好吧,我有点能感觉到你的失望,因为从str_word_count切换到preg_match_all并不容易。 但是你问问题的方式有点不精确,我还是试着回答它。不精确,因为你有大量的错误假设,你显然认为这是理所当然的(它发生在我们最好的人身上(。我希望我能稍微纠正一下:

$s = "THE UTF-8 NO-BREAK'xA0SPACE"; // a non-ASCII byte
preg_match_all('/[-'''p{L}]+/u',$s,$m);
var_dump($m);            // empty! (corrupted)

此代码是错误的。你在这里责怪 PHP 没有给出警告或其他东西,但我必须承认,这里唯一要责怪的是"你"。PHP 确实允许您检查错误。在你这么早就判断必须在错误处理中给出警告之前,我必须提醒你,有不同的方法来处理错误。一些处理是提供消息,另一种处理错误的方法是用返回值告诉它们。如果我们访问 preg_match_all 的手册页并查找返回值的文档,我们可以找到这个:

返回

完整模式匹配项的数量(可能为零(,如果发生错误,则返回 FALSE。

最后的部分:

如果发生错误,则为 FALSE [由我突出显示]

是错误处理中的一些常用方法,用于向调用代码发出发生某些错误的信号。让我们回顾一下您认为它不起作用的代码:

$s = "THE UTF-8 NO-BREAK'xA0SPACE"; // a non-ASCII byte
preg_match_all('/[-'''p{L}]+/u',$s,$m);
var_dump($m);            // empty! (corrupted)

这段代码唯一显示的是键入它的人(我猜是你(显然决定不进行任何错误处理。这很好,除非那个人也抗议代码不起作用。

可悲的是,这是一个常见的用户错误,如果你编写脆弱的代码(例如没有错误处理(,不要指望它能以可靠的方式工作。这永远不会发生。

那么当你编程时,这需要什么?首先,您应该了解您使用的功能。这通常需要了解输入参数和返回值。您会发现该信息通常记录在案。使用手册。其次,您实际上需要关心返回值并执行自己的错误处理。该函数本身不知道如果发生错误意味着什么。是个例外吗?然后,您可能需要执行异常处理,如演示示例中所示:

<?php
/**
 * @link http://stackoverflow.com/q/19316127/367456
 */
$s = "THE UTF-8 NO-BREAK'xA0SPACE"; // a non-ASCII byte
$result = preg_match_all('/[-'''p{L}]+/u',$s,$m);
if ($result === FALSE) {
    switch (preg_last_error()) {
        case PREG_BAD_UTF8_ERROR:
            throw new InvalidArgumentException(
                'UTF-8 encoded binary string expected.'
            );
        default:
            throw new RuntimeException('preg error occured.');
    }
}
var_dump($m);            // nothing at all corrupted...

无论如何,这意味着你需要看看你做了什么,了解它并编写更多的代码。没有魔法。没有错误。只是一点工作。

摆在你面前的另一部分可能是了解软件中的字符是什么,但这更独立于具体的编程语言,如PHP,例如你可以在这里进行介绍性阅读:

  • 关于字符代码问题的教程
  • 每个软件开发人员绝对、肯定地必须了解 Unicode 和字符集的绝对最低要求(没有任何借口!
第一个是必读的,

或者可能是必读的书签,因为它有很多要读的,但它很好地解释了这一切。