PHP中的字串、編碼、UTF-8

虞大膽發表於2016-12-13

  最近看了不少編碼方面的文章,所以分二篇博文說下“PHP、字串、編碼、UTF-8”相關知識,本篇博文是上半部分,分為四大塊內容,分別是“字串的定義和使用”、“字串轉換”、“PHP 字串的本質”、“多位元組字串”。上半部分比較基礎,下一篇文章《PHP 與 UTF-8的最佳實踐》可能幹貨更多一點。

 

 字串的定義和使用

  PHP 中能夠通過四種方法設定字串:

  單引號字串

  單引號字串類似於 Python 中的原始字串,也就是說單引號字串沒有變數解析功能和特殊字元轉義功能。比如$str='hello\nworld',其中的\n並沒有換行功能。

  雙引號字串

  雙引號字串具備單引號字串沒有的變數解析功能和特殊字元轉義功能。

  個人對於十六進位制和八進位制的字串特殊轉義很感興趣,特別補充:

\[0-7]{1,3} #八進位制表達方式
\x[0-9A-Fa-f]{1,2} #十六進位制表達方式

  heredoc

  這種表示式類似於 Python 中的長字串,能夠定義包含多行的字串。其語法定義很嚴格,使用起來需要注意。

$str=<<<EOD
hello\n
world
EOD;

  Nowdoc

  Nowdoc類似於單引號字串,不會解析變數。比較適合定義一大段文字且無需對其中的特殊字元進行轉義。

  變數解析

  PHP字串最強大的部分就是變數解析,可以在執行時根據上下文解析變數(這才是解釋型語言),可以產生很多妙用。

  簡單的變數解析就是在字串中可以包含“變數”,“陣列”,“物件屬性”,複雜的語法規則就是使用{}符號來進行操作(組成一個表示式)。

  通過一個例子看看變數解析的強大之處

class beers {
    const softdrink = 'softdrink';
    public static $ale = 'ale';
    public $data = array(1,3,"k"=>4);
}

$softdrink = "softdrink";
$ale = "ale";
$arr = array("arr1","arr2","arr3"=>"arr4","arr4"=>array(1,2));
$arr4 = "arr4";
$obj = new beers;
echo "line1:{$arr[1]}\n";
echo "line2:{$arr['arr4'][0]}\n"; 
echo "line3:{$obj->data[1]}\n";
echo "line4:{${$arr['arr3']}}\n";
echo "line5:{${$arr['arr3']}[1]}\n";
echo "line6:{${beers::softdrink}}\n";
echo "line7:{${beers::$ale}}\n";

 字串轉換

  PHP 語言比 Python 簡單的另外一個原因就是型別的隱式轉換,會簡化很多操作,這裡通過字串轉換來說明。

  字串型別強制轉換

$var = 10 ;
$dvar = (string)$var ;
echo $dvar . "_" . gettype($dvar);

  strval()函式是獲取變數的字串值:

$var = 10.2 ;
$dvar = strval($var) ;
echo gettype($var) . "_" . $dvar . "_" . gettype($dvar);

  settype()函式是設定變數的型別:

$str = "10hello";
settype($str, "integer");
echo $str ;

  在強制型別轉換過程中,將其他型別的值轉換為字串的時候會遵循一定的規則,比如一個布林值 boolean 的 TRUE 被轉換成 string 的 “1”。相關規則最好還是理解下。

  自動型別轉換

  上面的二個轉換屬於顯示轉換,而更要關注的是自動型別轉換,在一個需要字串的表示式中,會自動轉換為型別,具體見例子:

$bool = true;
$str = 10 + "hello"
echo $bool . "_" . $str ;

 PHP 字串的本質

  引用 PHP 文件的解釋:

PHP 中的 string 的實現方式是一個由位元組組成的陣列再加上一個整數指明緩衝區長度。並無如何將位元組轉換成字元的資訊,由程式設計師來決定。字串由什麼值構成沒有限制,包括值為 0 的位元組可以出現在字串的任何位置。

PHP並不特別指明字串的編碼,那字串到底是怎樣編碼的呢,這取決於程式設計師。字串會按照 PHP 檔案的編碼來對字串進行編碼。比如你的檔案編碼是 GBK,那麼你程式碼內容都是 GBK的。

  補充二進位制安全這個概念,其值為 0 (NULL)的位元組可以處於字串任何位置,而 PHP 的部分非二進位制函式底層是呼叫的 C 函式,會把 NULL 後面的字元忽略。

 只要 PHP 的檔案編碼是能相容 ASCII 的,那麼字串操作就可以很好的被處理。但是字串操作本質上還是 Native 的(不管檔案編碼是什麼),所以在使用的時候需要注意:

  • 某些函式假定字串是以單位元組編碼的,但並不需要將位元組解釋為特定的字元。比如 sbustr() 函式。
  • 很多函式是需要顯示的傳遞編碼引數,不然會從 PHP.INI 檔案中獲取預設值,比如 htmlentities() 函式。
  • 還有一些函式和本地區域有關,這些函式也只能是單位元組操作的。

  一般情況下,雖然 PHP 內部不支援 Unicode 字元,但是支援 UTF-8 編碼,絕大部分情況下不會有什麼問題,但是下列的情況可能就處理不了了:

  • 非 UTF-8 編碼字串如何進行轉換
  • 一個 UTF-8 編碼的網頁,但是使用者在提交表單的時候,可能使用 GBK 的編碼(不遵守 meta tag)
  • 一個 UTF-8 編碼的 PHP 檔案,使用 strlen("中國")返回的是 6 而不是實際的字元數(2)

  那麼如何解決該問題呢? PHP 提供了 mbstring 擴充套件 !

 多位元組字串

  mbstring 擴充套件預設不是開啟的,安裝的時候需要 --enable-mbstring。

  我們首先看看 PHP.INI 中對於 mbstring 指令的配置,花了好久才逐步明白。

  • mbstring.language 這個引數我就理解為 UTF-8 了
  • mbstring.internal_encoding 這個編碼和 PHP 檔案編碼沒有關係,只是在大部分 mbstring 函式裡面需要指定待處理字串的編碼,假如不顯示指定,預設就獲取該引數的值,該引數的值在高版本 PHP 中用 default_charset 引數代替了。
  • mbstring.http_input 該引數指定 HTTP input 的預設編碼(不包含 GET 引數)。一般和 HTML 頁面的編碼保持一致,該引數的值用 default_charset 引數代替。
  • mbstring.http_output 該引數誤導我了,HTTP output 是什麼,PHP 輸出不就是頁面,怎麼會有這概念?
  • mbstring.encoding_translation,這個引數重點說下,預設是關閉的,假如開啟,PHP 會對 POST 變數和上傳檔案的名稱自動轉換編碼為 mbstring.internal_encoding 指定的值,不過我沒有試驗過,大家可以上傳一箇中文名的檔案。建議關閉,讓程式設計師來處理相關問題。

  後面看看 mbstring 擴充套件的一些函式:

  • mb_http_input():檢測 HTTP input 字元編碼,覺得對於檔案上傳的檔名有必要處理。
  • mb_convert_encoding():比較常用的函式,注意第三個引數。
  • mb_detect_order():設定/獲取字元編碼的檢測順序。
  • mb_list_encodings():返回系統支援的編碼列表。

  重點說明下:PHP 檔案支援的編碼有一定要,要相容 ASCII。

  但是不要使用 BIG-5 作為 PHP 檔案編碼,尤其字串以 identifiers 或 literals 形式出現,假如實在 PHP 檔案編碼要是 BIG-5,那麼對於輸入輸出的內容儘量轉換為 UTF-8。

 Zend Multibyte

  最後說下 Zend Multibyte 這個概念,理解的不是特別深刻,首先不要和 mbstring 擴充套件混在一塊。 Zend Multibyte 模式預設是關閉的,可以通過 zend.multibyte 指令開啟。然後通過 declare() 函式來指定 PHP 解析器的編碼。

  那這個指令出現的意義是什麼?上面說過 PHP 檔案的編碼需要是相容 ASCII 的,那麼類似於 BIG-5 這樣的非相容 ASCII 編碼怎麼辦,可以通過這個指令來操作,當 PHP 解析器讀取 mbstring.script_encoding 編碼並用該編碼來解析 PHP 檔案。

  下一篇博文《PHP 與 UTF-8的最佳實踐》,有興趣的可以看下。

相關文章