PHP源文件编码与变量编码的判断

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='')  
在有办法获取变量(字符串)的编码时,可以改写成:
fu
nction 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('/xe4xb8x80/','一');
}

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('%^(?:
        [x09x0Ax0Dx20-x7E] # ASCII
        | [xC2-xDF][x80-xBF] # non-overlong 2-byte
        | xE0[xA0-xBF][x80-xBF] # excluding overlongs
        | [xE1-xECxEExEF][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);



原文地址:https://www.cnblogs.com/dreamyoung/p/php-var_charset.html