PHP 2021年04月26日 11:52   編集
長過ぎる文字列を切り詰めて短くしたいことがよくあるので、指定した文字数以上の文字は指定数に切り詰めて省略記号をつけるという処理のために以下のような関数を使っていましたが、思うように動かないことが多々ありました。
function jtruncate($str,$n) {
if ($n < 2) { return ; }
if (strlen($str) <= $n) { return $str ; }
$substr = mb_substr($str,0,intval($n/2),"UTF-8") . '...';
return $substr;
}
たとえばこの関数を使って
print jtruncate('PHPのコード',14);
を実行すると
PHPのコード...
と切り詰めていないはずなのに切り詰めた記号...が表示されてしまいます。
strlenは半角文字を1バイト、全角文字を2バイトとして計算すると思っていたのですが、これが勘違いだったようです。使用する文字コードによってstrlenが返す全角文字の長さはいろいろ変わってしまうようで、Shift-JISだと2バイトですが、UTF-8だと3文字と計算しているようです。[1]
mb_strlenを使えば半角文字も全角文字も同じ1文字として計算するので問題ないのですが、同じ文字数で切り詰めると
あいうえおか...
Abcdef...
のようになってしまいます。
文字を切り詰めるのは、限られたスペースにおさめるためという用途が多いので、文字列の見た目の長さをそろえるために幅の狭い半角文字は1、幅の広い全角文字は2としてカウントしたいところです。

このような処理の場合はmb_strwidthを使う必要があったようです。これは文字コードに関わりなく[2]半角文字を1、全角文字を2として長さを得られるようです。
上記の関数は
function jtruncate1($str,$n) {
if ($n < 2) { return ; }
if (mb_strwidth($str) <= $n) { return $str ; }
$substr = mb_substr($str,0,intval($n/2),"UTF-8") . '...';
return $substr;
}
とすると、目的どおり動くようになりました。
それと似た名前のmb_strimwidthという文字列を切り詰める関数もあるようです。
これを使うと
function jtruncate2($str,$n) {
if ($n < 2) { return ; }
if (mb_strwidth($str) <= $n) { return $str ; }
$substr = mb_strimwidth($str,0,$n) . '...';
return $substr;
}
となります。
上記のjtruncate1とjtruncate2は同じ動作をするかなと思ったのですが、
print jtruncate1('PHPのコード',6) . "<br>";
print jtruncate2('PHPのコード',6) . "<br>";
を実行すると、
PHP...
PHPの...
となります。
[1]UTF-8では、3以外の文字数とカウントされる全角文字もあるようです
[2]実際には第2パラメーターで文字コードを指定しない場合は内部文字エンコーディングが使用される
counter:3,760