PHP의 중국어, 이모티콘 또는 톤을 포함하는 라틴 문자를 비교하기 위해 strcmp ()를 사용하면 "비교 결과가 잘못된 것", "디스플레이는 물음표/작은 정사각형"및 "이상한 정렬"과 같은 현상을 겪게됩니다. 많은 사람들이 이것을 "brudled code"라고 부릅니다. 실제로, strcmp () 자체는 "캐릭터를 엉망으로 만들 수있는 능력이 없다. 단지 두 줄의"생 바이너리 "를 비교한다. 문제는 종종 일관되지 않은 문자 인코딩 , 다른 텍스트 정규화 또는 잘못된 비교 도구 에 있습니다.
다음은 한 번에 주요 이유와 운영 솔루션을 설명합니다.
바이너리 안전, 사례 민감성 : strcmp ($ a, $ b)는 바이트 순서로 $ a 와 $ b를 비교하여 <0 / 0 /> 0을 반환합니다. UTF-8, GBK, 이모티콘을 이해하지 못하거나 "알파벳 분류 규칙"을 이해하지 못하거나 케이스 폴딩 또는 악센트 처리도하지도 않습니다.
결론 : 두 줄이 다른 인코딩을 사용하거나 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 등)
동일한 "중국어", 하나는 UTF-8이고 다른 하나는 GBK이고 바이트는 완전히 다릅니다. strcmp () 는 바이트 만 본다. 물론 "잘못 배치된다".
UTF-8 BOM 및 보이지 않는 캐릭터 <br> 파일 헤더 또는 입력에 BOM (EF BB BF) , ZWSP (ZERL-Width Space) 및 보이지 않는 제어 문자를 삽입하면 첫 바이트/마지막 바이트를 다르게 만들어서 더 "터무니"가됩니다.
정규화 된 차이 (NFC/NFD)
é는 단일 문자 (NFC) 또는 "E + Combined Accent"(NFD) 일 수 있습니다. 인간의 눈은 동일하고 바이트는 다르고 strcmp ()는 비정상적으로 불평등하거나 분류 된 것으로 판단됩니다.
"인간 분류/언어 규칙"을 기대하지만 바이트 비교 <br>을 사용하십시오 중국인 Pinyin, German?, 프랑스 악센트, 일본 칸나 규칙으로 분류하고 싶습니까? strcmp ()는 이것을 이해하지 못하고 지역 비교 도구가 필요합니다.
입구 통합 : 데이터베이스 연결, HTTP 헤더, 템플릿 파일, CLI 환경, 전체 링크는 UTF-8 로 설정됩니다.
BOM/제어 문자 제거 :
<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 확장자를 설치/활성화하고 Normanizer를 사용하여 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 ()를 사용하십시오 (바이트, 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>"사용자 비주얼에서 문자 수/자르기/트래버스" : grapeme_* 시리즈 ( INTL 과 동일)를 사용하여 "Grameme Clusters"를 처리하여 이모티콘을 절반으로 절단하지 않도록하십시오.
<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">'카오 카오'</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">// 하나의 변형 만 보존됩니다</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 :: Normalize () .
비교 목표를 명확히하십시오 .
바이트 일관성 : strcmp/strcasecmp .
언어 규칙 정렬/평등 : intl \ Collator .
시각적 문자 수준 처리 : Grapheme_* .
데이터베이스는 HTTP 헤더와 일치합니다 . MySQL은 UTF8MB4 와 적절한 Collation (예 : UTF8MB4_0900_AI_CI ) 및 HTTP SET Content-Type : Text/HTML; charset = utf-8 .
"그것은 같은 UTF-8입니다. 왜 strcmp () 가 같지 않습니까?"
BOM, 제로 폭 2 자, 또는 하나는 NFC이고 다른 하나는 NFD와 혼합 될 수 있습니다. 먼저 청소 + 표준화.
" strcasecmp ()가 사례에 민감하게 국제화 될 수 있습니까?"
접이는 주로 ASCII 시맨틱입니다. 보다 신뢰할 수있는 실습 : MB_STRTOLOWER () 후 비교하거나 적절한 콜라터 의 강도를 사용하십시오.
"이모티콘/콤비네이션 단어는 잘린 또는 비정상적으로 계산됩니다."
Grapheme_strlen/Grapheme_Substr을 사용하고 Strlen/substr을 사용하여 사용자가 가시 가능한 문자를 처리하지 마십시오.