Lập trình · 24/09/2026

Độ dài chuỗi Unicode trong PHP: Byte, code point và grapheme

Giao diện báo tên người dùng còn đủ chỗ, nhưng backend lại từ chối vì vượt giới hạn ký tự. Một nguyên nhân thường gặp là hai phía đang đếm những đơn vị khác nhau: byte, Unicode code point hoặc cụm ký tự hiển thị.

Độ dài chuỗi Unicode trong PHP: Byte, code point và grapheme

Giao diện báo tên người dùng còn đủ chỗ, nhưng backend lại từ chối vì vượt giới hạn ký tự. Một nguyên nhân thường gặp là hai phía đang đếm những đơn vị khác nhau: byte, Unicode code point hoặc cụm ký tự hiển thị.

Bài viết dùng PHP với UTF-8, minh họa cách chọn đơn vị đếm trước khi viết validation. Mã mẫu cần hai extension mbstring và intl; không cần database hoặc kết nối mạng.

1. Ba câu hỏi khác nhau về độ dài

strlen trả số byte. mb_strlen đếm ký tự theo encoding; với UTF-8 hợp lệ, đây là số code point. grapheme_strlen đếm đơn vị grapheme và yêu cầu chuỗi UTF-8 hợp lệ.

Câu hỏiĐơn vị phù hợp để xem xét
Chuỗi chiếm bao nhiêu byte trong UTF-8?Byte
Có bao nhiêu code point?Code point
Người dùng cảm nhận bao nhiêu cụm ký tự?Grapheme cluster

Grapheme hữu ích cho một số giới hạn giao diện, nhưng không phải phép đo chiều rộng pixel hay số ô terminal. Hai chuỗi cùng số grapheme vẫn có thể rộng khác nhau khi render bằng font khác nhau.

2. Hai cách biểu diễn chữ nhìn giống nhau

Ví dụ dùng chữ é: một cách là code point dựng sẵn U+00E9; cách khác là chữ e đi với dấu sắc kết hợp U+0301. Cách biểu diễn khác nhau làm số byte và code point khác nhau, dù thường được hiển thị giống nhau. Việc đếm không tự chuyển chuỗi về cùng một dạng chuẩn hóa.

<?php
declare(strict_types=1);

if (!extension_loaded('mbstring') || !extension_loaded('intl')) {
    throw new RuntimeException('This example requires mbstring and intl.');
}
$samples = [
    'ASCII' => ['abc', [3, 3, 3]],
    'precomposed' => ["\u{00E9}", [2, 1, 1]],
    'combining' => ["e\u{0301}", [3, 2, 1]],
];
foreach ($samples as $label => [$value, $expected]) {
    if (!mb_check_encoding($value, 'UTF-8')) {
        throw new InvalidArgumentException('Invalid UTF-8');
    }
    $actual = [strlen($value), mb_strlen($value, 'UTF-8'), grapheme_strlen($value)];
    if ($actual !== $expected) {
        throw new RuntimeException('Unexpected result: '.$label);
    }
    printf("%s: bytes=%d codepoints=%d graphemes=%d\n", $label, ...$actual);
}

Lưu thành unicode-length.php và chạy php unicode-length.php trong môi trường có đủ extension. Kết quả của bộ mẫu là:

ASCII: bytes=3 codepoints=3 graphemes=3
precomposed: bytes=2 codepoints=1 graphemes=1
combining: bytes=3 codepoints=2 graphemes=1

Mỗi mẫu có kiểm tra kết quả kỳ vọng để tránh biến ví dụ thành bảng số không được xác minh. Trong ứng dụng thật, xử lý rõ trường hợp dữ liệu không phải UTF-8 hợp lệ và lỗi từ hàm đếm; không mặc định mọi đầu vào đều an toàn chỉ vì form dùng UTF-8.

3. Viết hợp đồng giới hạn trước

Đề xuất cho trường tên hiển thị: ghi rõ giới hạn theo đơn vị nào, khi nào trim khoảng trắng và có chuẩn hóa Unicode không. Giữ thứ tự xử lý nhất quán ở frontend, backend và test. Nếu chuẩn hóa, cần dùng giải pháp Unicode phù hợp; không bỏ dấu hoặc thay ký tự tùy tiện để làm chuỗi ngắn đi.

Giới hạn số grapheme không thay thế giới hạn kích thước request. Một cụm có thể chứa nhiều code point kết hợp; hệ thống vẫn cần giới hạn byte và tài nguyên xử lý phù hợp. Ngược lại, một giới hạn byte dành cho lưu trữ không nên được giao diện diễn đạt thành cùng số “ký tự” nếu cách đếm không trùng.

4. Cắt chuỗi cũng phải cùng đơn vị

Nếu chỉ đổi hàm đếm nhưng vẫn cắt theo byte, backend có thể tạo ra chuỗi UTF-8 hỏng. Nếu cắt theo code point, vẫn có thể tách dấu kết hợp khỏi ký tự đứng trước. Chọn hàm cắt tương ứng mục đích và kiểm thử dấu, emoji, chuỗi rỗng cùng ranh giới độ dài.

Không tự cắt mật khẩu, token hoặc định danh để vừa giới hạn. Với các trường đó, từ chối đầu vào không hợp lệ bằng thông báo rõ ràng thường phù hợp hơn việc âm thầm thay đổi giá trị.

5. Bộ kiểm thử tối thiểu

  • ASCII thông thường và chuỗi rỗng.
  • Tiếng Việt có dấu cùng các biểu diễn kết hợp.
  • Emoji đơn và chuỗi emoji ghép.
  • Đúng giới hạn, thiếu một đơn vị và vượt một đơn vị.
  • Đầu vào UTF-8 không hợp lệ từ nguồn tích hợp.
  • Frontend và backend cho cùng quyết định theo hợp đồng đã chọn.

Với chuỗi emoji phức tạp, giữ môi trường Unicode/ICU của triển khai được kiểm soát và chạy test khi nâng cấp. Đừng dùng kết quả của ba mẫu đơn giản để suy ra mọi chuỗi đều đã được bao phủ.

Không có một hàm độ dài tốt nhất cho mọi tình huống. Điều quan trọng là chọn đúng đơn vị, diễn đạt rõ cho người dùng và áp dụng nhất quán trên toàn bộ đường đi dữ liệu.

Thảo luận

Bình luận 0

Đăng nhập để bình luận

Bạn cần có tài khoản để tham gia thảo luận và trả lời độc giả khác.

Đăng nhậpĐăng ký

Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.