STRCMP()を使用して、PHPに中国語、絵文字、またはトーンを含むラテン文字を比較すると、「比較結果が正しくありません」、「ディスプレイは疑問符/小さな正方形」、「奇妙な並べ替え」などの現象に遭遇します。多くの人がこの「文字化けコード」と集合的に呼んでいます。実際、 Strcmp()自体には「キャラクターを台無しにする」能力はありません。2つの文字列の「生のバイナリ」を比較するだけです。問題は、多くの場合、一貫性のない文字エンコード、異なるテキストの正規化、または間違った比較ツールにあります。
以下は、主要な理由と運用ソリューションを一度に説明しています。
バイナリセーフ、ケースセンシティブ: Strcmp($ a、$ b)は$ aと$ bをバイト順に比較し、 <0/0 /> 0を返します。 UTF-8、GBK、絵文字を理解しておらず、「アルファベット順の並べ替えルール」を理解しておらず、折りたたみやアクセント処理も理解していません。
結論:2つの文字列が異なるエンコーディングを使用している場合、または両方ともUTF-8であるが、異なるバイトシーケンス(BOM、異なる正規化された形式など)がある場合、 Strcmp()の結果は「不合理に見えます」。
<span><span><span class="hljs-meta"><?php</span></span><span>
</span><span><span class="hljs-title function_ invoke__">var_dump</span></span><span>(</span><span><span class="hljs-title function_ invoke__">strcmp</span></span><span>(</span><span><span class="hljs-string">"a"</span></span><span>, </span><span><span class="hljs-string">"b"</span></span><span>)); </span><span><span class="hljs-comment">// int(-1) 普通:a < b</span></span><span>
</span><span><span class="hljs-title function_ invoke__">var_dump</span></span><span>(</span><span><span class="hljs-title function_ invoke__">strcmp</span></span><span>(</span><span><span class="hljs-string">"中国語"</span></span><span>, </span><span><span class="hljs-string">"キャラクター"</span></span><span>)); </span><span><span class="hljs-comment">// 比較的 UTF-8 的キャラクター节序,結果が一貫していない場合があります“中国語キャラクター拼音顺序”</span></span><span>
</span></span>一貫性のないエンコーディング(UTF-8対GBKなど)
同じ「中国語」、1つはUTF-8、もう1つはGBKであり、バイトは完全に異なります。 strcmp()は、バイトのみを見ます。もちろん「置き忘れ」。
UTF-8 BOMと目に見えないキャラクター<BR> BOM(EF BB BF) 、ゼロ幅空間(ZWSP)、および目に見えないコントロール文字をファイルヘッダーまたは入力に挿入すると、最初のバイト/最後のバイトが異なり、より「とんでもない」になります。
正規化された違い(NFC/NFD)
éは単一の文字(NFC)または「E +結合アクセント」(NFD)になります。人間の目は同じで、バイトは異なり、 strcmp()は不平等であるか、異常にソートされていると判断されます。
「人間の並べ替え/言語ルール」を期待しますが、byte比較<br>を使用します 中国のピニイン、ドイツ語?、フランス語のアクセント、日本のカンナのルールで並べ替えたいですか? Strcmp()はこれらを理解しておらず、地域の比較ツールが必要です。
入場統合:データベース接続、HTTPヘッダー、テンプレートファイル、CLI環境、完全なリンクはUTF-8に設定されています。
BOM/CONTROL文字を削除します:
<span><span><span class="hljs-meta"><?php</span></span><span>
</span><span><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span> </span><span><span class="hljs-title">strip_bom_and_controls</span></span><span>(</span><span><span class="hljs-params"><span class="hljs-keyword">string</span></span></span><span> </span><span><span class="hljs-variable">$s</span></span><span>): </span><span><span class="hljs-title">string</span></span><span> {
</span><span><span class="hljs-comment">// 行く BOM</span></span><span>
</span><span><span class="hljs-variable">$s</span></span><span> = </span><span><span class="hljs-title function_ invoke__">preg_replace</span></span><span>(</span><span><span class="hljs-string">'/^\xEF\xBB\xBF/'</span></span><span>, </span><span><span class="hljs-string">''</span></span><span>, </span><span><span class="hljs-variable">$s</span></span><span>);
</span><span><span class="hljs-comment">// 行く常见零宽キャラクター符:ZWSP, ZWNJ, ZWJ, NBSP …</span></span><span>
</span><span><span class="hljs-variable">$s</span></span><span> = </span><span><span class="hljs-title function_ invoke__">preg_replace</span></span><span>(</span><span><span class="hljs-string">'/[\x{200B}\x{200C}\x{200D}\x{00A0}]/u'</span></span><span>, </span><span><span class="hljs-string">''</span></span><span>, </span><span><span class="hljs-variable">$s</span></span><span>);
</span><span><span class="hljs-keyword">return</span></span><span> </span><span><span class="hljs-variable">$s</span></span><span>;
}
</span></span>必要に応じて変換:
<span><span><span class="hljs-meta"><?php</span></span><span>
</span><span><span class="hljs-variable">$clean</span></span><span> = </span><span><span class="hljs-title function_ invoke__">mb_convert_encoding</span></span><span>(</span><span><span class="hljs-variable">$input</span></span><span>, </span><span><span class="hljs-string">'UTF-8'</span></span><span>, </span><span><span class="hljs-string">'UTF-8,GBK,GB2312,BIG5,ISO-8859-1'</span></span><span>);
</span></span>INTL拡張機能をインストール/有効にし、 normalizerを使用してNFCに統合します。
<span><span><span class="hljs-meta"><?php</span></span><span>
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">class_exists</span></span><span>(</span><span><span class="hljs-string">'Normalizer'</span></span><span>)) {
</span><span><span class="hljs-variable">$a</span></span><span> = </span><span><span class="hljs-title class_">Normalizer</span></span><span>::</span><span><span class="hljs-title function_ invoke__">normalize</span></span><span>(</span><span><span class="hljs-variable">$a</span></span><span>, </span><span><span class="hljs-title class_">Normalizer</span></span><span>::</span><span><span class="hljs-variable constant_">FORM_C</span></span><span>);
</span><span><span class="hljs-variable">$b</span></span><span> = </span><span><span class="hljs-title class_">Normalizer</span></span><span>::</span><span><span class="hljs-title function_ invoke__">normalize</span></span><span>(</span><span><span class="hljs-variable">$b</span></span><span>, </span><span><span class="hljs-title class_">Normalizer</span></span><span>::</span><span><span class="hljs-variable constant_">FORM_C</span></span><span>);
}
</span></span>それでも「バイトの一貫性」 : strcmp()を使用します。または、ケースに依存しないバイトの比較が必要で、 strcasecmp()を使用します(BYTE、ASCIIルールで同じ)。
人間の読み取り可能/言語ルールの比較(並べ替え/重複排除/発見) : intl \ collator (地域比較、アクセント、大文字、大文字、バリアントの理解)を使用します。
<span><span><span class="hljs-meta"><?php</span></span><span>
</span><span><span class="hljs-variable">$coll</span></span><span> = </span><span><span class="hljs-keyword">new</span></span><span> </span><span><span class="hljs-title class_">\Collator</span></span><span>(</span><span><span class="hljs-string">'zh_CN'</span></span><span>); </span><span><span class="hljs-comment">// または 'zh-Hans-CN', 'en_US', 'de_DE' 待って</span></span><span>
</span><span><span class="hljs-variable">$coll</span></span><span>-></span><span><span class="hljs-title function_ invoke__">setStrength</span></span><span>(</span><span><span class="hljs-title class_">\Collator</span></span><span>::</span><span><span class="hljs-variable constant_">SECONDARY</span></span><span>); </span><span><span class="hljs-comment">// 忽略大小写但区分重音待って</span></span><span>
</span><span><span class="hljs-title function_ invoke__">var_dump</span></span><span>(</span><span><span class="hljs-variable">$coll</span></span><span>-></span><span><span class="hljs-title function_ invoke__">compare</span></span><span>(</span><span><span class="hljs-string">'中国語'</span></span><span>, </span><span><span class="hljs-string">'キャラクター'</span></span><span>)); </span><span><span class="hljs-comment">// -1/0/1,言語ルールに基づいています</span></span><span>
</span></span>「ユーザービジュアルでのキャラクターの数/切り捨て/トラバーサル」 : grapheme_* series( intlと同じ)を使用して「グラメメクラスター」を処理して、絵文字を半分に切断しないようにします。
<span><span><span class="hljs-meta"><?php</span></span><span>
</span><span><span class="hljs-variable">$text</span></span><span> = </span><span><span class="hljs-string">"?????発達"</span></span><span>; </span><span><span class="hljs-comment">// 含む ZWJ コネクタ emoji</span></span><span>
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-title function_ invoke__">grapheme_substr</span></span><span>(</span><span><span class="hljs-variable">$text</span></span><span>, </span><span><span class="hljs-number">0</span></span><span>, </span><span><span class="hljs-number">2</span></span><span>); </span><span><span class="hljs-comment">// ?????開ける</span></span><span>
</span></span>無視する必要があるマルチバイト比較:比較する前にMB_STRTOLOWERまたはMB_CONVERT_CASEを使用します(最初に正規化することを忘れないでください):
<span><span><span class="hljs-meta"><?php</span></span><span>
</span><span><span class="hljs-variable">$a</span></span><span> = </span><span><span class="hljs-title class_">Normalizer</span></span><span>::</span><span><span class="hljs-title function_ invoke__">normalize</span></span><span>(</span><span><span class="hljs-variable">$a</span></span><span>, </span><span><span class="hljs-title class_">Normalizer</span></span><span>::</span><span><span class="hljs-variable constant_">FORM_C</span></span><span>);
</span><span><span class="hljs-variable">$b</span></span><span> = </span><span><span class="hljs-title class_">Normalizer</span></span><span>::</span><span><span class="hljs-title function_ invoke__">normalize</span></span><span>(</span><span><span class="hljs-variable">$b</span></span><span>, </span><span><span class="hljs-title class_">Normalizer</span></span><span>::</span><span><span class="hljs-variable constant_">FORM_C</span></span><span>);
</span><span><span class="hljs-variable">$aFold</span></span><span> = </span><span><span class="hljs-title function_ invoke__">mb_strtolower</span></span><span>(</span><span><span class="hljs-variable">$a</span></span><span>, </span><span><span class="hljs-string">'UTF-8'</span></span><span>);
</span><span><span class="hljs-variable">$bFold</span></span><span> = </span><span><span class="hljs-title function_ invoke__">mb_strtolower</span></span><span>(</span><span><span class="hljs-variable">$b</span></span><span>, </span><span><span class="hljs-string">'UTF-8'</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">var_dump</span></span><span>(</span><span><span class="hljs-title function_ invoke__">strcmp</span></span><span>(</span><span><span class="hljs-variable">$aFold</span></span><span>, </span><span><span class="hljs-variable">$bFold</span></span><span>) === </span><span><span class="hljs-number">0</span></span><span>);
</span></span><span><span><span class="hljs-meta"><?php</span></span><span>
</span><span><span class="hljs-variable">$names</span></span><span> = [</span><span><span class="hljs-string">'チャン・サン'</span></span><span>, </span><span><span class="hljs-string">'li si'</span></span><span>, </span><span><span class="hljs-string">'王ウー'</span></span><span>, </span><span><span class="hljs-string">'アリ'</span></span><span>, </span><span><span class="hljs-string">'Cao Cao'</span></span><span>];
</span><span><span class="hljs-variable">$coll</span></span><span> = </span><span><span class="hljs-keyword">new</span></span><span> </span><span><span class="hljs-title class_">\Collator</span></span><span>(</span><span><span class="hljs-string">'zh_CN@collation=pinyin'</span></span><span>); </span><span><span class="hljs-comment">// システムが必要です ICU サポート</span></span><span>
</span><span><span class="hljs-variable">$coll</span></span><span>-></span><span><span class="hljs-title function_ invoke__">sort</span></span><span>(</span><span><span class="hljs-variable">$names</span></span><span>);
</span><span><span class="hljs-title function_ invoke__">print_r</span></span><span>(</span><span><span class="hljs-variable">$names</span></span><span>);
</span></span> <span><span><span class="hljs-meta"><?php</span></span><span>
</span><span><span class="hljs-variable">$input</span></span><span> = [</span><span><span class="hljs-string">'café'</span></span><span>, </span><span><span class="hljs-string">'Cafe'</span></span><span>, </span><span><span class="hljs-string">'CAFé'</span></span><span>, </span><span><span class="hljs-string">'cafe'</span></span><span>];
</span><span><span class="hljs-variable">$coll</span></span><span> = </span><span><span class="hljs-keyword">new</span></span><span> </span><span><span class="hljs-title class_">\Collator</span></span><span>(</span><span><span class="hljs-string">'fr_FR'</span></span><span>);
</span><span><span class="hljs-variable">$coll</span></span><span>-></span><span><span class="hljs-title function_ invoke__">setStrength</span></span><span>(</span><span><span class="hljs-title class_">\Collator</span></span><span>::</span><span><span class="hljs-variable constant_">PRIMARY</span></span><span>); </span><span><span class="hljs-comment">// アクセントと大文字のケースを無視します</span></span><span>
</span><span><span class="hljs-variable">$unique</span></span><span> = [];
</span><span><span class="hljs-keyword">foreach</span></span><span> (</span><span><span class="hljs-variable">$input</span></span><span> </span><span><span class="hljs-keyword">as</span></span><span> </span><span><span class="hljs-variable">$s</span></span><span>) {
</span><span><span class="hljs-variable">$sN</span></span><span> = </span><span><span class="hljs-title class_">Normalizer</span></span><span>::</span><span><span class="hljs-title function_ invoke__">normalize</span></span><span>(</span><span><span class="hljs-variable">$s</span></span><span>, </span><span><span class="hljs-title class_">Normalizer</span></span><span>::</span><span><span class="hljs-variable constant_">FORM_C</span></span><span>);
</span><span><span class="hljs-variable">$found</span></span><span> = </span><span><span class="hljs-literal">false</span></span><span>;
</span><span><span class="hljs-keyword">foreach</span></span><span> (</span><span><span class="hljs-variable">$unique</span></span><span> </span><span><span class="hljs-keyword">as</span></span><span> </span><span><span class="hljs-variable">$u</span></span><span>) {
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$coll</span></span><span>-></span><span><span class="hljs-title function_ invoke__">compare</span></span><span>(</span><span><span class="hljs-variable">$sN</span></span><span>, </span><span><span class="hljs-variable">$u</span></span><span>) === </span><span><span class="hljs-number">0</span></span><span>) { </span><span><span class="hljs-variable">$found</span></span><span> = </span><span><span class="hljs-literal">true</span></span><span>; </span><span><span class="hljs-keyword">break</span></span><span>; }
}
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-variable">$found</span></span><span>) </span><span><span class="hljs-variable">$unique</span></span><span>[] = </span><span><span class="hljs-variable">$sN</span></span><span>;
}
</span><span><span class="hljs-title function_ invoke__">print_r</span></span><span>(</span><span><span class="hljs-variable">$unique</span></span><span>); </span><span><span class="hljs-comment">// 保存されているバリアントは1つだけです</span></span><span>
</span></span>エンコードの確認: mb_detect_encoding($ s、['utf-8'、 'gbk'、 'big5'、 'iso-8859-1']、true) 。わからない場合は、最初にUTF-8に転送します。
BOM/Zero-width文字に移動します:上記のstrip_bom_and_controls()を参照してください。
NFCに正規化: normalizer :: remormize() 。
比較目標を明確にします。
バイトの一貫性: strcmp/strcasecmp 。
言語ルールソート/平等: intl \ collator 。
視覚文字レベルの処理: grapheme_* 。
データベースは、HTTPヘッダーと一致しています。MySQLはUTF8MB4および適切な照合( UTF8MB4_0900_AI_CIなど)、およびHTTPセットコンテンツタイプ:テキスト/HTMLを使用します。 charset = utf-8 。
「それは同じUTF-8です。なぜstrcmp()が等しくないのですか?」
BOM、ゼロ幅の文字、または1つはNFC、もう1つはNFDと混合される場合があります。クリーン +標準化されています。
「 STRCASECMP()は、ケース非感受性で国際化できますか?」
その折り目は主にASCIIセマンティクスです。より信頼性の高い練習: MB_STRTOLOWER()の後に比較するか、 Collatorの適切な強度を使用します。
「絵文字/コンビネーションワードは切り捨てられたり、異常にカウントされたりします」
grapheme_strlen/grapheme_substrを使用し、 strlen/substrを使用してユーザー可視文字を処理しません。