PHP中文乱码的原因通常在于header()设置的编码与PHP文件的编码不一致。
PHP文件的编码影响了其变量的编码,在默认的情况下,PHP文件是什么编码,其变量也就是什么编码。
而当将变量我们输出到网页时,如果与header()设置的编码不一致,也就产生乱码。
虽然变量最开始时与PHP源文件一致,但是有些时候,一些方法(函数)的操作,会改变变量的编码方式。
另外,有些接收性的变量,不一定与PHP源文件的编码或header()的编码一样。
这样,当变量编码与PHP源文件不同时,我们就需要进行一步类似于以下的操作:
$myVar= iconv("$myVar当前编码",“PHP源文件的编码”,$myVar);
即将变量转化为与PHP源文件一致的编码。
很多时候,这句代码是出现在方法(函数)中的,如果需要适应PHP源文件是UTF-8与GBK。即无论源文件是UTF-8或GBK,该方法(函数)代码都不需要进行修改,并且如果需要,可以在调用端加入一个输入性编码来表示当前变量编码。
这时候,我们不是在方法(函数)中直接写死成:
$myVar = iconv("$myVar当前编码","UTF-8",$myVar) 或 $myVar = iconv("$myVar当前编码","GBK",$myVar),而是一般作以下类似的处理(只考虑GB2312及UTF-8编码):
function doSth($myVar,$input_charset){ $is_utf8 = isutf8();//判断PHP源文件是否UTF-8编码 if($is_utf8){ if(strtoupper($input_charset) != 'UTF-8'){ $myVar = iconv($input_charset,'UTF-8',$myVar); } } else {//php-GBK if(strtoupper($input_charset) != 'GB2312') { $myVar = iconv($input_charset, 'GB2312',$myVar); } } //do sth with $myVar ... }
在没有自动判断变量(一般指字符串)是哪种编码的情况下,客户端调用就只能用:doSth($myVar,'UTF-8')或doSth($myVar,‘GB2312’)显然,每次调用都需要附带编码。
为了节省麻烦,通常有两种手段,一是使用判断变量(字符串)编码的方法自动判断变量(字符串)编码(需要自己实现),二是给予默认值。
这里先说二,对$input_charset给个默认值,由于需要兼容UTF-8或GBK的源文件,所以默认值给‘UTF-8’或'GB2312'都是不太合适的。
最好的办法是给空字符串作为默认值,在方法内部判断,当为空字符串时,让它的默认值与PHP源文件的编码一致(只考虑GB2312及UTF-8编码)。
function doSth($myVar,$input_charset=''){ $is_utf8 = isutf8(); if(empty($input_charset)){ $input_charset = $is_utf8 ? 'UTF-8' : 'GB2312'; } if($is_utf8){ if(strtoupper($input_charset) != 'UTF-8'){ $myVar = iconv($input_charset,'UTF-8',$myVar); } } else {//php-GBK if(strtoupper($input_charset) != 'GB2312') { $myVar = iconv($input_charset, 'GB2312',$myVar); } } //do sth with $myVar ... }
这时,客户端调用就用:doSth($myVar); 如果遇到乱码,就证明变量实际编码与默认编码(PHP文件编码)不一致。这时不能使用默认值了,只能指定实际编码,如:doSth($myVar,'UTF-8'); 或 doSth($myVar,'GB2312');
PHP源文件为UTF-8编码,$myVar实际编码也为UTF-8编码,header()设置字符编码也为UTF-8编码,doSth($myVar);执行过程不会进行编码转换,运行结果不产生乱码。
PHP源文件为UTF-8编码,$myVar实际编码也为UTF-8编码,header()设置字符编码也为GBK编码,doSth($myVar);执行过程不会进行编码转换,运行结果产生乱码
因为在没有进行输出的编码转换的时候,默认的输出结果是与PHP源文件保持一致的,所以,也可以添加另外一个参数,即输出编码$output_charset(让其默认为空,也处理为默认为PHP文件编码),
此时方法改写为(只考虑GB2312及UTF-8编码):
function doSth($myVar,$input_charset='',$output_charset=''){ $is_utf8 = isutf8(); if(empty($input_charset)){ $input_charset = $is_utf8 ? 'UTF-8' : 'GB2312'; } if($is_utf8){ if(strtoupper($input_charset) != 'UTF-8'){ $myVar = iconv($input_charset,'UTF-8//IGNORE',$myVar); } } else {//php-GBK if(strtoupper($input_charset) != 'GB2312') { $myVar = iconv($input_charset, 'GB2312//IGNORE',$myVar); } } if(empty($output_charset)){ $output_charset = $is_utf8 ? 'UTF-8' : 'GB2312'; } if($is_utf8){ if(strtoupper($output_charset) != 'UTF-8'){ $myVar = iconv('UTF-8',$output_charset.'//IGNORE',$myVar); } } else {//php-GBK if(strtoupper($output_charset) != 'GB2312') { $myVar = iconv('GB2312',$output_charset.'//IGNORE',$myVar); } } //do sth with $myVar ... }
要使上边结果不乱码,可以调用:doSth($myVar,'','GB2312');
PHP源文件为UTF-8编码,$myVar实际编码为GB2312编码,doSth($myVar, 'GB2312'); 可以产生UTF-8编码的l输出结果,然后再视header()设置的字符编码,决定输出的编码参数。
如果header()设置编码为UTF-8,那么:doSth($myVar,'GB2312');执行结果不会产生乱码。
如果header()设置编码为GB2312,那么:doSth($myVar,'GB2312','GB2312') 或 doSth($myVar) 也不会产生乱码。
至此,先总结一下:
doSth($myVar,$input_charset='',$output_charset='') 对于UTF-8编码的源文件,其调用方式:
doSth($myVar);//当$myVar为UTF-8,header设置的也为UTF-8时
doSth($myVar,'','GB2312');//当$myVar为UTF-8,header设置的为GB2312时
doSth($myVar,'GB2312');//当$myVar为GB2312,header设置为UTF-8时
doSth($myVar,'GB2312','GB2312');//当$myVar为GB2312,header设置为GB2312时。不过,由于两次转换的原因,也可以直接用:doSth($myVar);
doSth($myVar,$input_charset='',$output_charset='') 对于GB2312编码的源文件,其调用方式:
doSth($myVar);//当$myVar为GB2312,header()设置也为GB2312时
doSth($myVar,'','UTF-8');//当$myVar为GB2312, header()设置为UTF-8时
doSth($myVar,'UTF-8');//当$myVar为UTF-8,header设置为GB2312时
doSth($myVar,'UTF-8','UTF-8');//当$myVar为UTF-8,header设置也为UTF-8时。不过由于两次转换的原因,也可以直接用:doSth($myVar);
至此,可以用5种方式来进行调用,并且可以发现:
如果header()与变量实际编码一致或者header()与PHP源文件编码一致,则无需指定输出编码。
如果源文件编码与变量实际编码一致的话,则无需指定输入编码。
UTF-8文件编码时,输入编码或输出编码可能需要指定为'GB2312'
GB2312文件编码时,输入编码或输出编码可能需要指定为'UTF-8'
由于PHP虽然可以判断源文件编码,但对于header()设置了什么编码是无法通过代码判断的,所以$output_charset目前而言,是无法省略的,但$input_charset(即变量的编码),是可以通过代码判断的(虽然目前很多代码无法做到完美)
function doSth($myVar,$input_charset='',$output_charset='')
在有办法获取变量(字符串)的编码时,可以改写成:
function doSth($myVar, $output_charset='')
function doSth($myVar,$output_charset=''){ $is_utf8 = isutf8(); //判断PHP文件编码 $input_charset = var_charset($myVar);//判断变量编码 if($is_utf8){ if(strtoupper($input_charset) != 'UTF-8'){ $myVar = iconv($input_charset,'UTF-8//IGNORE',$myVar); } } else {//php-GBK if(strtoupper($input_charset) != 'GB2312') { $myVar = iconv($input_charset, 'GB2312//IGNORE',$myVar); } } if(empty($output_charset)){ $output_charset = $is_utf8 ? 'UTF-8' : 'GB2312'; } if($is_utf8){ if(strtoupper($output_charset) != 'UTF-8'){ $myVar = iconv('UTF-8',$output_charset.'//IGNORE',$myVar); } } else {//php-GBK if(strtoupper($output_charset) != 'GB2312') { $myVar = iconv('GB2312',$output_charset.'//IGNORE',$myVar); } } //do sth with $myVar ... }
下边将对isutf8()方法及var_charset($str)方法进行实现:
isutf8()方法还是比较容易的,下边进行探测实现:
function isutf8(){ //返回true或false }
汉字“一” 在PHP源文件为UTF-8时,调用:json_encode("一“) 会正确的返回含UNICODE编码的字符串 "\u4e00"
而在PHP源文件为GBK时,调用:json_encode("一“) 会不正确的返回 "\u04bb"
所以,我们直接可以用此方法来判断源文件编码是否UTF-8:
function isutf8(){ return json_encode('一')=='"\u4e00"'; }
当然,还有其它各种手段来判断,原理类似:
function isutf8(){ return json_decode('"\u4e00"') == "一"; } function isutf8(){ return bin2hex('一')=='e4b880'; } function isutf8(){ return strlen('一')==3; } function isutf8(){ return preg_match('/\xe4\xb8\x80/','一'); } function isutf8(){ return pack('a','一')==chr(0xe4); } function isutf8(){ return ord('一')==0xe4; } function isutf8(){ return chr(0xe4).chr(0xb8).chr(0x80)=='一'; } function isutf8(){ return chr(0xe4) =='一'[0]; } function isutf8(){ return "\xe4" ==substr('一',0,1); }
类似地,还可以参照地实现isgbk()等。
var_charset($str)方法目前比较流行于网络的实现为:
function var_charset($string) { $charset = ['ASCII','GB2312','GBK','UTF-8']; foreach($charset as $c) { if ($string === @iconv($c, $c . '//IGNORE', $string)) { return $c; } } return null; }
或者:
function var_charset($string){ return [ '0'=>NULL, 'ASCII'=>'ASCII', 'EUC-CN'=>'GB2312', 'CP936'=>'GBK', 'UTF-8'=>'UTF-8' ][mb_detect_encoding($string,array('ASCII','GB2312','GBK','UTF-8'))]; }
iconv()以及mb_detect_encoding()来判断的方式其实不是特别完美,有时不准确。
另外还有这种判断UTF-8的对于非中文环境来说,确实更准确的,但对于中文环境来说,也是不准确的:
function is_utf8str($string) { // From http://w3.org/International/questions/qa-forms-utf-8.html return preg_match('%^(?: [\x09\x0A\x0D\x20-\x7E] # ASCII | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 )*$%xs', $string); } function var_charset($string){ return is_utf8str($string) ? 'UTF-8' : 'GB2312'; }
另外,自己实现了这种,针对中文环境来实现的判断UTF-8,同时也可以适应英文环境(中文环境中不使用第二个参数):
function is_utf8str($str,$utf8all2bits=false){ $bit2 = true; $bit3 = false; $bit4 = false; $allCN = array(); $len = strlen($str); for($i=0;$i<$len;$i++){ $c1 = substr($str,$i,1); if(ord($c1)<=0x7F) continue; if(ord($c1)>=0xFF) return false; $flag1_2 = ord($c1)>=0xc0 && ord($c1)<=0xdf; $flag1_3 = ord($c1)>=0xe0 && ord($c1)<=0xef; $flag1_4 = ord($c1)>=0xf0 && ord($c1)<=0xf7; if(!($flag1_2 || $flag1_3 || $flag1_4) || $i==$len-1) return false; $c2 = substr($str,$i+1,1); $flag2 = ord($c2)>=0x80 && ord($c2)<=0xbf; if(!$flag2) return false; if($flag1_2){ $bit2 = true; $allCN[] = (ord($c1)>=0xB0 && ord($c1)<=0xF7 && ord($c2)>=0xA1 && ord($c2)<=0xFE) ? 1 : 0; if($i==$len-2) {if($bit3 || $bit4) return true; $N = count($allCN); if($N>0){ for($n=0;$n<$N;$n++){ if($allCN[$n]!=1){ return true; } } } return $utf8all2bits; } $i=$i+1; }else{ if($i==$len-2) return false; $c3 = substr($str,$i+2,1); $flag3 = ord($c3)>=0x80 && ord($c3)<=0xbf; if(!$flag3) return false; if($flag1_3){ $bit3 = true; if($bit2 || $bit4 || $i==$len-3) return true; $i=$i+2; }else{ $bit4 = true; if($i==$len-3) return false; $c4 = substr($str,$i+3,1); $flag4 = ord($c4)>=0x80 && ord($c4)<=0xbf; if(!$flag4) return false; if($bit2 || $bit3 || $i==$len-4) return true; $i=$i+3; } } } return true; } function var_charset($string){ return is_utf8str($string) ? 'UTF-8' : 'GB2312'; }
解决守isutf8()和var_charset()后,doSth()的调用如下:
doSth($myVar,$output_charset='') 对于UTF-8编码的源文件,其调用方式:
doSth($myVar);//当header设置为UTF-8时
doSth($myVar,'GB2312');//当header设置的为GB2312时
doSth($myVar,$output_charset='') 对于GB2312编码的源文件,其调用方式:
doSth($myVar);//当header设置的也为GB2312时
doSth($myVar,'UTF-8');//当header设置的为UTF-8时
至此,可以用3种方式来进行调用,并且可以发现:
如果header()与变量实际编码一致或者header()与PHP源文件编码一致,则无需指定输出编码。
UTF-8文件编码时,输出编码可能需要指定为'GB2312'
GB2312文件编码时,输出编码可能需要指定为'UTF-8'
所以,乱码的时候,直接更换一下输出编码就可以了。三种调用方式总结:
doSth($myVar); //PHP源文件与header()设置一致时
doSth($myVar,'GB2312');//PHP源文件为UTF-8,header()设置为GB2312时
doSth($myVar,'UTF-8'); //PHP源文件为GB2312,header()设置为UTF-8时
这样的话,当header()设置的该编码与PHP源文件不一致的时候,header()设置了什么编码,$output_charset就指定为什么编码,这样就不会有乱码输出。
上边的doSth($myVar,$output_charset)假定了对$myVar需要进行页面输出的情况,如果只对$myVar进行操作而不进行页面输出的话,可以简化(只考虑GB2312及UTF-8编码):
function doSth($myVar,$output_charset=''){ $is_utf8 = isutf8(); //判断PHP文件编码 $input_charset = var_charset($myVar);//判断变量编码 if($is_utf8){ if(strtoupper($input_charset) != 'UTF-8'){ $myVar = iconv($input_charset,'UTF-8//IGNORE',$myVar); } } else {//php-GBK if(strtoupper($input_charset) != 'GB2312') { $myVar = iconv($input_charset, 'GB2312//IGNORE',$myVar); } } }
自动判断变量编码,不考虑输出的调用,最为简单: doSth($myVar);