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);を使って変換します。
それでも駄目な場合、有志ライブラリを使用するか、上記のように自前の変換スクリプトを作成して置き換えを行う必要があります。
参考リンク
- やっかいな漢字 – CJK部首補助/康煕部首
「CJK部首補助または康煕部首を解決する一覧」を使わせて頂きました。 - リアルタイム文字コード変換/解析ツール


