6

NO-BREAK SPACE 和许多其他 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
4

2 回答 2

4

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

PS:关于“PHP5 的内置库行为一致性”的讨论,我的结论是 PHP5 并没有那么糟糕,但是我们已经创建了很多用户定义的 wrap(façade)函数(参见 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。对于这项任务,我使用了@bobinceiconv提示

 /**
  * 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);
于 2013-10-11T11:20:19.363 回答
3

好的,我能感觉到你的失望,因为从 切换str_word_countpreg_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 等具体的编程语言,例如,您可以在此处阅读介绍性内容:

第一个是必读或必读的书签,因为它有很多要读的东西,但它很好地解释了这一切。

于 2013-10-11T19:53:56.120 回答