想必 iconv 這個(gè)擴(kuò)展的相關(guān)函數(shù)大家多少都接觸過,做為 PHP 的默認(rèn)擴(kuò)展它已經(jīng)存在了很久,也是我們在操作字符編碼時(shí)經(jīng)常會使用的函數(shù)。不過除了 iconv() 這個(gè)函數(shù)外,你還知道它的其它函數(shù)嗎?今天,我們就來學(xué)習(xí)一下 iconv 擴(kuò)展中的各種好玩的函數(shù)。
iconv 設(shè)置及獲取信息
首先,就是我們可以設(shè)置 iconv 擴(kuò)展中默認(rèn)定義的輸出和輸出字符編碼格式。
iconv_set_encoding("internal_encoding", "UTF-8");
// Deprecated: iconv_set_encoding(): Use of iconv.internal_encoding is deprecated
iconv_set_encoding("output_encoding", "ISO-8859-1");
// Deprecated: iconv_set_encoding(): Use of iconv.output_encoding is deprecated
var_dump(iconv_get_encoding());
// array(3) {
// ["input_encoding"]=>
// string(5) "UTF-8"
// ["output_encoding"]=>
// string(10) "ISO-8859-1"
// ["internal_encoding"]=>
// string(5) "UTF-8"
// }
iconv_set_encoding() 接收兩個(gè)參數(shù),一個(gè)是設(shè)置的屬性類型,一個(gè)是設(shè)置的編碼格式。屬性類型包括 internal_encoding 、 input_encoding 和 output_encoding ,分別代表內(nèi)部的、輸入的、輸出的編碼格式。在這段測試代碼中,我們將 internal_encoding 設(shè)置為 UTF8 ,將 output_encoding 設(shè)置為 ISO-8859-1 ,然后使用 iconv_get_encoding() 打印出當(dāng)前環(huán)境中相關(guān)的 iconv 屬性設(shè)置信息,可以看到,在默認(rèn)情況下當(dāng)前環(huán)境中的 input_encoding 也是 UTF8 格式。
不過需要說明的是,iconv_set_encoding() 已經(jīng)是不推薦使用的函數(shù)了,或者說不推薦使用這個(gè)函數(shù)來設(shè)置上面的三種屬性類型,它們會報(bào)出過時(shí)警告信息?,F(xiàn)在更推薦直接使用 php.ini 中的 default_charset 來進(jìn)行設(shè)置。
iconv 根據(jù)編碼獲取字符長度、指定位置及截取字符串
在面對中文字符串的操作時(shí),我們使用默認(rèn)的 strlen() 之類的函數(shù)返回的中文字符長度是不正確的,這就牽涉到編碼的問題。一般情況下,UTF8 是占三個(gè)字節(jié),而 GBK 是占兩個(gè)字節(jié),所以說一個(gè)漢字對于 strlen() 來說如果是在 UTF8 環(huán)境中會返回 3 。當(dāng)然,現(xiàn)在大多數(shù)情況下我們會使用 MB 庫擴(kuò)展的相關(guān)函數(shù)來處理這種問題,不過 iconv 也為我們提供了幾個(gè)用于字符串操作的函數(shù)。
echo iconv_strlen("測試長度測試長度"), PHP_EOL; // 8
echo iconv_strlen("測試長度測試長度", 'ISO-8859-1'), PHP_EOL; // 24
echo iconv_strlen("測試長度測試長度", 'GBK'), PHP_EOL; // 12
echo '======', PHP_EOL;
echo iconv_strpos("測試長度測試長度", "長"), PHP_EOL; // 2
echo iconv_strpos("測試長度測試長度", "長", 0, 'ISO-8859-1'), PHP_EOL; // 6
echo iconv_strpos("測試長度測試長度", "長", 0, 'GBK'), PHP_EOL; //
echo '======', PHP_EOL;
echo iconv_strrpos("測試長度測試長度", "長"), PHP_EOL; // 6
echo iconv_strrpos("測試長度測試長度", "長", 'ISO-8859-1'), PHP_EOL; // 18
echo '======', PHP_EOL;
echo iconv_substr("測試長度測試長度", 2, 4), PHP_EOL; // 長度測試
echo iconv_substr("測試長度測試長度", 6, 12, 'ISO-8859-1'), PHP_EOL; // 長度測試
echo iconv_substr("測試長度測試長度", 3, 6, 'GBK'), PHP_EOL; // 長度測試
iconv_strlen() 就是獲取字符串長度的,如果不給第二個(gè)參數(shù)就按默認(rèn)的字符集編碼來獲取字符串長度。在測試代碼中可以看出,同樣八個(gè)中文字的內(nèi)容,使用不同的編碼返回的數(shù)量是不相同的。在這里,我們發(fā)現(xiàn) iconv 中對于 GBK 的中文是 1.5 個(gè)字節(jié),也就是 8 個(gè)中文字占用了 12 個(gè)字節(jié)的長度。
iconv_strpos() 和 iconv_strrpos() 和 strpos() 的作用一樣,返回某個(gè)字符第一次出現(xiàn)的位置,一個(gè)是從前往后(從左往右),另一個(gè)是從后往前(從右往左)。它們的第三個(gè)參數(shù)是偏移量,也就是查找到指定字符后再偏移幾個(gè)單位。從這里我們可以看出,對于 GBK 編碼的操作是有問題的,因?yàn)樵?iconv 中,GBK 是 1.5 個(gè)字節(jié),這樣會帶來單個(gè)字符無法定位的問題。
iconv_substr() 很明顯地就是截取字符串的函數(shù)了,同樣我們要根據(jù)編碼格式來指定它的截取位置。
iconv 轉(zhuǎn)換字符編碼
接下來就是本尊 iconv() 函數(shù)的使用的了,其實(shí)它反而沒什么可講的,將指定的編碼轉(zhuǎn)換成另外一種編碼而已,相信這個(gè)函數(shù)大家都不陌生。
$phone = file_get_contents('https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=13888888888');
print_r($phone);
// __GetZoneResult_ = {
// mts:'1388888',
// province:'????',
// catName:'?й????',
// telString:'13888888888',
// areaVid:'30515',
// ispVid:'3236139',
// carrier:'???????'
// }
print_r(iconv('GBK', 'UTF-8', $phone));
// __GetZoneResult_ = {
// mts:'1388888',
// province:'云南',
// catName:'中國移動',
// telString:'13888888888',
// areaVid:'30515',
// ispVid:'3236139',
// carrier:'云南移動'
// }
print_r(iconv('GBK', 'ISO-8859-1//IGNORE', $phone));
// __GetZoneResult_ = {
// mts:'1388888',
// province:'',
// catName:'',
// telString:'13888888888',
// areaVid:'30515',
// ispVid:'3236139',
// carrier:''
// }
我們找到的這個(gè)淘寶用于查找手機(jī)號相關(guān)信息的開放接口,返回的正好是 GBK 類型的數(shù)據(jù)。當(dāng)我們直接打印結(jié)果時(shí),在 UTF8 環(huán)境下它就會輸出亂碼信息。這時(shí),我們通過 iconv() 函數(shù)就能夠輕松地將編碼轉(zhuǎn)換成 UTF8 格式,并正確打印出了結(jié)果。第三個(gè)測試中,我們在要轉(zhuǎn)換到的字符集編碼類型后面加上了 //IGNORE ,目的就是忽略無法轉(zhuǎn)換的內(nèi)容,所以可以看出在最后我們轉(zhuǎn)換到錯(cuò)誤的 ISO-8859-1 時(shí),中文信息就全都沒有了,因?yàn)樗鼈儫o法轉(zhuǎn)換就被忽略掉了。
mime 郵件頭操作
最后我們再看一個(gè)非常不常用的內(nèi)容,那就是 iconv 還可以直接轉(zhuǎn)換 mime 頭中的編碼內(nèi)容信息。這個(gè) mime 頭信息其實(shí)就是標(biāo)示當(dāng)前文件或者內(nèi)容的 mime 類型。平常我們會根據(jù)它來判斷上傳的文件是否正確,除些之外,在郵件發(fā)送中,這個(gè) mime 頭的使用也非常廣泛。如果做過郵件發(fā)送接收相關(guān)的開發(fā)并且抓過包的同學(xué)一定見過下面的內(nèi)容。
headers_string = <<<EOF
Subject: =?UTF-8?B?UHLDvGZ1bmcgUHLDvGZ1bmc=?=
To: example@example.com
Date: Thu, 1 Jan 1970 00:00:00 +0000
Message-Id: <example@example.com>
Received: from localhost (localhost [127.0.0.1]) by localhost
with SMTP id example for <example@example.com>;
Thu, 1 Jan 1970 00:00:00 +0000 (UTC)
(envelope-from example-return-0000-example=example.com@example.com)
Received: (qmail 0 invoked by uid 65534); 1 Thu 2003 00:00:00 +0000
EOF;
Subject 字符就是郵件的標(biāo)題,To 就是發(fā)送人的郵件地址。在這里我們主要看一下 Subject 的內(nèi)容,它的開頭就有一段描述這個(gè)字段使用的編碼信息的內(nèi)容,?UTF-8 ,然后后面是一堆看不懂的東西。其實(shí)我們簡單地能看出來這是一個(gè) base64 編碼的內(nèi)容,如果將它解碼在對應(yīng)的編碼內(nèi)容下就能看到原文信息。不過,這個(gè)時(shí)候我們也可以使用 iconv 來直接轉(zhuǎn)換它的編碼。
$headers = iconv_mime_decode_headers($headers_string, 0, "ISO-8859-1");
var_dump($headers);
// array(5) {
// ["Subject"]=>
// string(15) "Pr?fung Pr?fung"
// ["To"]=>
// string(19) "example@example.com"
// ["Date"]=>
// string(30) "Thu, 1 Jan 1970 00:00:00 +0000"
// ["Message-Id"]=>
// string(21) "<example@example.com>"
// ["Received"]=>
// array(2) {
// [0]=>
// string(204) "from localhost (localhost [127.0.0.1]) by localhost with SMTP id example for <example@example.com>; Thu, 1 Jan 1970 00:00:00 +0000 (UTC) (envelope-from example-return-0000-example=example.com@example.com)"
// [1]=>
// string(57) "(qmail 0 invoked by uid 65534); 1 Thu 2003 00:00:00 +0000"
// }
// }
看到了么?不僅直接轉(zhuǎn)了編碼,而且還將 mime 頭格式轉(zhuǎn)換成了 PHP 中的數(shù)組格式。當(dāng)然,我們這里測試的代碼是將正常的內(nèi)容轉(zhuǎn)換到 ISO-8859-1 了,反而是出現(xiàn)了亂碼。下面我們再拿一個(gè)中文郵件的例子來看下。
$headers_string = <<<EOF
Return-Path: <bluesky7810@163.com>
Delivered-To: bhw98@sina.com
Received: (qmail 75513 invoked by alias); 20 May 2002 02:19:53 -0000
Received: from unknown (HELO bluesky) (61.155.118.135)
by 202.106.187.143 with SMTP; 20 May 2002 02:19:53 -0000
Message-ID: <007f01c3111c$742fec00$0100007f@bluesky>
From: "=?gb2312?B?wLbAtrXEzOwNCg==?=" <bluesky7810@163.com>
To: "bhw98" <bhw98@sina.com>
Cc: <bhwang@jlonline.com>
Subject: =?gb2312?B?ztK1xLbgtK6/2rPM0PI=?=
Date: Sat, 20 May 2002 10:03:36 +0800
MIME-Version: 1.0
Content-Type: multipart/mixed;
boundary="----=_NextPart_000_007A_01C3115F.80DFC5E0"
EOF;
$headers = iconv_mime_decode_headers($headers_string, 0, "UTF-8");
var_dump($headers);
// array(11) {
// ["Return-Path"]=>
// string(21) "<bluesky7810@163.com>"
// ["Delivered-To"]=>
// string(14) "bhw98@sina.com"
// ["Received"]=>
// array(2) {
// [0]=>
// string(58) "(qmail 75513 invoked by alias); 20 May 2002 02:19:53 -0000"
// [1]=>
// string(101) "from unknown (HELO bluesky) (61.155.118.135) by 202.106.187.143 with SMTP; 20 May 2002 02:19:53 -0000"
// }
// ["Message-ID"]=>
// string(40) "<007f01c3111c$742fec00$0100007f@bluesky>"
// ["From"]=>
// string(38) ""藍(lán)藍(lán)的天
// " <bluesky7810@163.com>"
// ["To"]=>
// string(24) ""bhw98" <bhw98@sina.com>"
// ["Cc"]=>
// string(21) "<bhwang@jlonline.com>"
// ["Subject"]=>
// string(21) "我的多串口程序"
// ["Date"]=>
// string(31) "Sat, 20 May 2002 10:03:36 +0800"
// ["MIME-Version"]=>
// string(3) "1.0"
// ["Content-Type"]=>
// string(16) "multipart/mixed;"
// }
這個(gè)中文郵件 mime 頭的 Subject 指定的是 GB2312 。通過 iconv_mime_decode_headers() 函數(shù)我們將整個(gè)頭信息中的內(nèi)容都轉(zhuǎn)換成了 UTF8 ,這時(shí)就可以正常顯示所有的內(nèi)容信息了。當(dāng)然,我們也可以對單個(gè)的 mime 字段進(jìn)行轉(zhuǎn)碼。
echo iconv_mime_decode("Subject: =?gb2312?B?ztK1xLbgtK6/2rPM0PI=?=", 0, 'UTF-8'), PHP_EOL; // Subject: 我的多串口程序
除了對于接收的信息進(jìn)行編碼轉(zhuǎn)換之外,我們還可以自己編碼相關(guān)的內(nèi)容進(jìn)行發(fā)送使用。
$preferences = array(
"input-charset" => "UTF-8",
"output-charset" => "GBK",
"line-length" => 76,
"line-break-chars" => "\n"
);
$preferences["scheme"] = "Q";
echo iconv_mime_encode("Subject", "測試頭", $preferences), PHP_EOL;
// Subject: =?GBK?Q?=B2=E2=CA=D4=CD=B7?=
$preferences["scheme"] = "B";
echo iconv_mime_encode("Subject", "測試頭", $preferences), PHP_EOL;
// Subject: =?GBK?B?suLK1M23?=
iconv_mime_encode() 函數(shù)就是用于進(jìn)行 mime 頭編碼的函數(shù)。第一個(gè)參數(shù)是 mime 字段名,第二個(gè)參數(shù)是字段值,第三個(gè)函數(shù)就是我們進(jìn)行編碼的參數(shù)了。編碼參數(shù)的內(nèi)容通過字段名就可以看出來,從什么編碼轉(zhuǎn)換成什么編碼,行的長度多少,換行符是什么。另外它還有一個(gè) scheme 字段,就是用于指定編碼結(jié)果的類型,如果設(shè)置的是 B ,那么編碼結(jié)果就會再加一層 base64 操作。
總結(jié)
是不是感覺奇怪的小姿勢又增加了呀?沒錯(cuò),在沒刷文檔之前我也只知道一個(gè) iconv 而已。甚至在學(xué)習(xí)了這些內(nèi)容之后我才發(fā)現(xiàn)了郵件信息原來是這樣編碼的,自己都感覺自己一下子高大上了。好了,不說廢話了,自己動手試試吧!
測試代碼:
參考文檔: