字符串损坏或preg_match错误?

时间:2013-10-11 10:39:08

标签: php utf-8

NO-BREAK SPACE和许多其他UTF-8符号need 2 bytes to its representation;因此,在假设的UTF8字符串的上下文中,非ASCII(> 127)的隔离(不是xC2之前)字节是一个不可识别的字符......好吧,它只是一个布局问题(!),但是会破坏整个字符串吗?

如何避免这种“非预期行为”? (occurs in some functions and not in others)。

示例(仅使用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

2 个答案:

答案 0 :(得分:4)

这不是一个完整的答案,因为我没有说为什么有些PHP函数“完全失效地编码无效字符串”而其他人没有:在问题的评论和@hakre回答中查看@deceze。 如果您正在寻找str_word_count()的PCRE替代品,请参阅下面的preg_word_count()

PS:关于“PHP5的内置库行为一致性”讨论,我的结论是PHP5并不是那么糟糕,但我们已经创建了很多用户定义的wrap(façade)函数(参见PHP-framworks的多样性!)......或者等待PHP6 :-)


谢谢@pebbl!如果我理解您的链接,there are a lack of error messagens on PHP。所以我的插图问题的一个可能的解决方法是添加一个错误条件...我找到the condition here(它确保有效的utf8!)...并且感谢@deceze记住存在一个内置函数来检查这个条件(我之后编辑了代码)。

将问题放在一起,将解决方案转换为函数(EDITED,感谢@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 tip

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

展示(try other inputs!):

 $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);

答案 1 :(得分:3)

好的,从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)

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

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

那么你编程时需要什么?首先,您应该了解您使用的功能。这通常需要有关输入参数和返回值的知识。您发现通常记录的信息。使用手册。其次,您实际上需要关心返回值并执行自己的错误处理。如果发生错误,单独的功能不知道它意味着什么。这是个例外吗?然后你需要像the demo example中一样进行异常处理:

<?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之类的具体编程语言,例如你可以在这里进行介绍性阅读:

第一个是必须阅读或者必须书签,因为它需要阅读很多,但它解释得非常好。