Pages

Friday, March 30, 2018

PHP và UTF-8

Hiện PHP chưa (thật sự) hỗ trợ Unicode ở tầng thấp, do đó, để hiển thị được chuỗi UTF-8, bắt buộc phải có thêm các thao tác xử lý bổ sung, trên web, HTML, SQL.

Bài này mình tóm lược lại về UTF8 và PHP.

UTF-8 ở chính ngôn ngữ PHP

Giả sử ta có character a, á, ư. Thử nghiệm với hai cặp hàm strlen()/mb_strlen() và strpos()/mb_strpos().

Kết quả với hàm tính lượng bytes có kết quả tương ứng như sau:

echo strlen('a'); //1
echo mb_strlen('a'); //1
echo strlen('á'); //2
echo mb_strlen('á'); //1

Ở đây có thể tạm kết luận, với các string multi-bytes (chẳng hạn theo chuẩn Unicode như tiếng Việt),  số lượng bytes sẽ khác nhau giữa hàm strlen() và mb_strlen().
 
Kết quả với hàm tìm character có trong string:

echo strpos("mana", "a"); //1
echo strpos("mán", "á"); //1
echo mb_strpos("mana", "a"); //1

echo strpos("mưán", "á"); //3
echo mb_strpos("mưán", "á"); //2


echo strpos("mán ư", "ư"); //5
echo mb_strpos("mán ư", "ư"); //4

Hàm tìm character cũng vậy, có sự khác biệt rất lớn giữa hàm strpos() và mb_strpos().

Như vậy ta buộc phải dùng các hàm có dạng mb_* khi xử lý string Unicode, đây là các hàm chuyên trị cho Unicode. Tuy nhiên, không phải hàm xử lý chuỗi nào cũng có hàm mb_ tương ứng.

Bạn có thể thiết lập mb_internal_encoding() ở đầu mỗi file PHP và hàm mb_http_output() ngay ở vị trí PHP xuất ra dữ liệu ngoài trình duyệt.

Ngoài ra, nhiều hàm PHP xử lý string có thể có thêm tham số xác định dạng mã hóa. Chẳng hạn như  htmlentities().

UTF-8 ở trên hệ điều hành

Hiện nay PHP có thể chạy ở hầu hết các hệ điều hành, gồm cả Linux và Windows. Nhưng cách PHP xử lý tên file ở mỗi hệ điều hành là có thể khác nhau, trong đó, hỗ trợ Linux tốt nhất. Chẳng hạn trên Windows, nếu dùng PHP tạo một file với mã non-ASCII, lỗi có thể xuất hiện. Trên Linux và OSX, bạn có thể mã hóa tên file dạng UTF-8, nhưng trên Windows, buộc phải dùng chuẩn ISO-8859-1.

UTF-8 khi mần ăn với MySQ 

Lưu ý 1: Để chắc chắn là chuỗi trên PHP sang MYSQL lưu ở định dạng UTF-8, hãy thiết lập character và collation là utf8bm4 (theo kinh nghiệm của mình có khả năng hiển thị các kí tự "lạ" nhiều hơn UTF8 thông thường, chẳng hạn có lần mình làm về phiên âm tiếng Anh thì chỉ khi thiết lập utf8bm4, hệ thống mới hiển thị được).

Lưu ý 2: Phải thiết lập trong kết nối với mysql khi viết bằng PHP. Giờ thì chắc là toàn dân xài PDO nên mình có thể thiết lập như sau:


$db
= new PDO('dblib:host=your_hostname;dbname=your_db;charset=UTF-8', $user, $pass);


Với PHP 5.3.6, tùy chọn charset chưa có nên có thể thực hiện theo tùy chọn sau:


$pdo = new PDO(
    'mysql:host=hostname;dbname=defaultDbName',
    'username',
    'password',
    array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")
);

Với mysqli, ta buộc phải thiết lập như sau:

$conn = mysqli_connect('localhost','db_username','password','your_database_name');
 mysqli_set_charset($conn,"utf8"); 

UTF-8 trên trình duyệt

Để chắc chắn là PHP sẽ xuất UTF-8, có thể dùng hàm mb_http_output(). Trên trình duyệt thì ta thường thiết lập  tùy chọn meta charset như sau, đặt trong thẻ <head>
  <meta charset="utf-8">

https://phpbestpractices.org/#utf-8

https://stackoverflow.com/questions/4475548/pdo-mysql-and-broken-utf-8-encoding