Edited at

u (PCRE_UTF8) オプションをつけないと \h、\s、\v で想定しない文字を破壊するおそれがある

More than 5 years have passed since last update.

PCRE 関数のエスケープシーケンスのうち、\h (水平方向の空白文字)、\s (空白文字)、\v (垂直方向の空白文字) を使う場合、UTF-8 のオプション (u)をつけないと、preg_replace などで不要な文字を削除する際に想定しない文字を破壊するおそれがあります。

次のコードはひらがなの「だ」(U+3060) が破壊される様子を示したものです。「だ」の3番目のバイト (0xA0) がノーブレークスペース (U+00A0) と誤検出されてしまいます。

<?php

$str = "だ";

var_dump(
"\xE3\x81\xA0" === $str,
"\xE3\x81" === preg_replace("/\h/", "", $str),
"e381" === bin2hex(preg_replace("/\h/", "", $str)),
$str === preg_replace("/\h/u", "", $str)
);

Unicode で定義されるすべての文字 (U+0000 から U+10FFFF) に対してこれらのエスケープシーケンスを試すと、膨大な数の文字がマッチすることがわかります。u オプションをつけた場合でも、複数の文字がマッチしますので、それらの1つ1つが本当に想定した種類の文字であるのかどうかを検討する必要があります。u オプションをつけると、次の文字がマッチします。

U+0009 U+0020 U+00A0 U+1680 U+180E U+2000

U+2001 U+2002 U+2003 U+2004 U+2005 U+2006
U+2007 U+2008 U+2009 U+200A U+202F U+205F
U+3000

<?php

print_matched_chars(function($char) {
return preg_match('/\h/', $char);
});

function print_matched_chars(callable $callable) {

for ($i = 0; $i < 0x110000; ++$i) {

if ($i > 0xD7FF && $i < 0xE000) {
continue;
}

$char = utf8_chr($i);
$hex = strtoupper(dechex($i));

if ($callable($char)) {
echo 'U+',
$i < 0x1000 ? str_repeat('0', 4 - strlen($hex)) : '',
$hex, ': ' , $char, PHP_EOL;
}

}
}

function utf8_chr($cp) {

if (!is_int($cp)) {
exit("$cp is not integer\n");
}

if ($cp < 0 || (0xD7FF < $cp && $cp < 0xE000) || 0x10FFFF < $cp) {
exit("$cp is out of range\n");
}

return mb_decode_numericentity('&#'.$cp.';', [0, 0x10FFFF, 0, 0x10FFFF], 'UTF-8');
}