PHPで見た目が同じ違う漢字に立ち向かう

掲載日
更新日

経緯

あるシンプルな検索機能(SQLで LIKE '%キーワード%'としているような本当に単純な検索)で、タイトルに「市民」と入っているにも関わらず、「市民」で検索しても引っかからない記事があるというお問い合わせを受けました。

文字コード変換ツールを実行したスクリーンショット。

解析ツールサイトを使って文字コードを調査したところ、どうも「民」の文字コードが異なっており、通常の「民」のUnicodeは「E6 B0 91」となるところを、「E2 BA A0」となっておりました。

CJK部首補助というもので、本来部首を表すための漢字とのこと。
漢字の解説などで使用するケース以外ではほぼほぼ使用しないと思われます。

元の入力が誤っていなくても、PDF等から漢字をコピーペーストするとこうなることがあるようです。
そして少なくとも自分の構築したサイトで使用しているフォント上はまったく同じ形で出力されてしまうため、ブラウザ上では判別できず非常に厄介。
 

さくらエディタに部首補助漢字を貼り付けて見た目に違うことが分かるプレビュー画像。

添付のようにさくらエディタに張り付ければ見た目の違いも分かります。

解決

文字コードやフォントについて詳しくないですが、こういったものは最初の問合せのような「確かにその文字が入っている(ように見える)のに検索に引っかからない」事態にならないためにも、全て一般的な漢字に変換したいです。

PHPにはNormalizerというクラスがあり、文字列の正規化が可能です。

正規化とは、文字や文字列を正式に決められた表現に変換する処理のことです。 これは、検索や並べ替えなどの際に比較をするときに特に重要となります。 ただ、それだけではなく、テキストを保存する際に表記を統一するために用いることもあります。

Normalizer クラスより引用

とのことなので、PHPで以下の処理を挟んであげれば問題ないのでは?と思いましたがそうは問屋が卸さない。

$normalized = Normalizer::normalize($str, Normalizer::FORM_C);

以下のような確認用のスクリプトを作って実行したところ、直っておらず。

<?php
$bad = "⺠";
$normalized = Normalizer::normalize($bad, Normalizer::FORM_C);

echo "→になってて欲しい: 民\n";
echo sprintf("%s => %s\n", $bad, $normalized);

echo sprintf("%s \n", $bad === $normalized ? "直ってない" : "直った");
スクリプトを実行した結果のスクリーンショット。民が部首補助漢字のままであることが確認できる。

 

調べたところ、「部首文字と統合漢字は別扱い」とのこと。難しい…。

統合漢字ではない(別物扱いなので、正規化のマップが無い)ため、こういったものは手動で変換ツールを作るか、有志のライブラリを使用するしかないようです。

今回は「やっかいな漢字 – CJK部首補助/康煕部首」のCJK部首補助または康煕部首を解決する一覧.txtを使用させて頂いて、自前で変換する処理を作りました。
マップ部分が長いので、全文はGitHubに掲載してあります。

class CJK
{
	const MAP = [
		"\u{2E83}" => "\u{4E5A}",
		// (略)
	];

	public static function convert($text)
	{
		return strtr($text, self::CJK_MAP);
	}
}

MAP定数を定義し、上記リンク先のtxtファイルの文字コードのマップを作成します。
strtr関数は第二引数に配列を指定すると、キーに一致する部分に対応する値に置き換えしてくれるので、第一引数に問題の漢字、第二引数に上記マップを指定して置き換えるだけのシンプルな処理です。

 

CJK::convert("民");のように呼び出して使用できます。

確認用スクリプトでも、require_once等で該当ファイルを読み込み、処理を実行するようにすると、添付スクリーンショットの通り普通の漢字に直ります。

スクリプトを実行した結果のスクリーンショット。「民」が一般的な漢字に戻ったことが確認できる。

まとめ

確かにその文字があるはずなのに、SQLの検索やCtrl+Fの検索に引っ掛からない場合、文字コード変換して一般的な文字コードになっているか確認します。

Normalize等で変換できるような漢字であれば、Normalizer::normalize($str, Normalizer::FORM_C);を使って変換します。

それでも駄目な場合、有志ライブラリを使用するか、上記のように自前の変換スクリプトを作成して置き換えを行う必要があります。

参考リンク

    • BlueSkyで共有する
    • Hatenaで共有する
記事の作成者のW.Aのアイコン

この記事を書いた人

W.A
茨城県在住Webエンジニアです。 PHP、PostgreSQL、Linuxなどを業務で使用しています。 趣味ではGoやNuxt、Flutterをやってます。
  • GitHubのリンク

Comment

関連記事

  • CentOS7でPHP8.2.35をコンパイルして使うのサムネイル画像

    CentOS7でPHP8.2.35をコンパイルして使う

    掲載

    CentOS7環境でCVE-2026-109618、 CVE-2026-109619対応のPHPのバージョンをコンパイルして使えるようにする方法の備忘録です。(※ご利用は自己責任で、何かあっても責任は負いかねます。必ず仮想マシンなどで本番と同じ環境を作って試してから本番作業を行ってください。)

  • 絵文字のアクセシビリティのサムネイル画像

    絵文字のアクセシビリティ

    掲載

    生成AIの登場で絵文字がふんだんにあしらわれた記事を見かけることが増えました。絵文字はスクリーンリーダーではどのように読み上げられ、どのようなアクセシビリティ対策が出来るのか、また推奨されているのか調査し、PHPを使ってaria-hiddenでマークアップする方法をまとめました。

  • PHP cURLのデフォルトタイムアウトは0(無制限)のサムネイル画像

    PHP cURLのデフォルトタイムアウトは0(無制限)

    掲載

    PHPのcURL処理では、デフォルトではタイムアウトの設定が0(無制限)となっており、転送中はタイムアウトしません。実行したAPIの不具合やネットワークの不調等でプロセスが残り続けて思わぬ障害を発生させるため、タイムアウト設定とその確認方法の備忘録。

check コピーしました!