0
0

More than 1 year has passed since last update.

htmlspecialchars で文字数を求める

Last updated at Posted at 2022-10-14

PHP ではマルチバイトの文字数を求めるのに mb_strlen が使われますが、本体にもマルチバイトを扱う機能があります。次のコードで htmlspecialchars で文字数を求めてみます。

コード

$str = 'あいうえお';
var_dump(5 === str_len($str, 'UTF-8'));

function str_len(string $str, string $encoding = 'UTF-8'): int
{
    $str = str_scrub($str, $encoding);
    $size = strlen($str);

    $length = 0;
    $char = '';

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

        $char .= $str[$i];

        if (str_valid_encoding($char, $encoding)) {

            ++$length;
            $char = '';

        }

    }

    return $length;
}


function str_valid_encoding(string $str, string $encoding = 'UTF-8'): bool
{
    return $str === htmlspecialchars_decode(htmlspecialchars($str, ENT_QUOTES, $encoding));
}

function str_scrub(string $str, string $encoding = 'UTF-8'): string
{
    return htmlspecialchars_decode(htmlspecialchars($str, ENT_SUBSTITUTE, $encoding));
}

コードの解説

最初にユーザー定義関数の str_scrub を使って不正なバイト列を置き換えます。ENT_SUBSTITUTE フラグを指定しています。PHP 8.1 であれば ENT_SUBSTITUTE はデフォルトフラグなので、指定する必要はありません。同等の機能をもつ mb_scrub は PHP 7.2.0 とそれ以降のバージョンで利用できます。

for ループのなかでは配列アクセス構文を使い、前から1バイトずつ取り出して連結し、正しい文字エンコーディングであるかどうかを str_valid_encoding でチェックしています。文字はそのままではわかりづらいので、バイト列の16進数表記を bin2hex で求めてみます。

// e3 81 82 e3 81 84 e3 81 86 e3 81 88 e3 81 8a"
var_dump(bin2hex('あいうえお'));

「あいうえお」は次のように表記することもできます。

// あいうえお
var_dump("\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a");

1文字目の「あ」の処理について調べてみましょう。「あ」を構成するバイトは16進数で「e3 81 82」です。
1、2回目のループで $char に投入された「0xe3」および「0xe3 0x81」は「あ」の一部しかない不正なバイト列なので、str_valid_encoding の判定は false になります。3回目のループで $charは「e3 81 82」のバイト列になり、正しい文字エンコーディングなので、$length の値は 1 増えます。計算が終わったら $char を空にして次の文字に備えます。

冗長ですが、次のように「あ」はチェックされました。

// false
// false
// true

var_dump(
    str_valid_encoding("\xe3"),
    str_valid_encoding("\xe3\x81"),
    str_valid_encoding("\xe3\x81\x82")
);

function str_valid_encoding(string $str, string $encoding = 'UTF-8'): bool
{
    return $str === htmlspecialchars_decode(htmlspecialchars($str, ENT_QUOTES, $encoding));
}

最後にパフォーマンスに関して、1文字ずつバイトを取り出す必要があるので、mb_strlen よりもだいぶ遅くなりますので、実用では使い物にはなりません。文字コードの学習教材にとどめておくべきでしょう。次の簡易ベンチマークコードでは40倍以上の速度の差がありました。

echo 'str_len', PHP_EOL,
benchmark(function() {
    str_len("あいうえお");
}, 300000), PHP_EOL;


echo 'mb_strlen', PHP_EOL,
benchmark(function() {
    mb_strlen("あいうえお");
}, 300000), PHP_EOL;

function benchmark(callable $callable, int $runs)
{
    $start = microtime(true);
    while(--$runs) {
        $callable();
    }
    $stop = microtime(true);

    return $stop - $start;
}

結果

str_len
0.67694401741028
mb_strlen
0.013685941696167
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0