PHP i UTF-8

> Dodaj do ulubionych

Nie ma jednego idealnego rozwiązania. Trzeba uważać, mieć oko na szczegóły i być konsekwentnym.

UTF-8 w PHP to porażka. Przykro mi.

Aktualnie PHP nie obsługuje standardu Unicode na niskim poziomie. Oczywiście są sposoby na zapewnienie prawidłowego przetwarzania łańcuchów UTF-8, ale są one skomplikowane i wymagają grzebania w prawie wszystkich poziomach aplikacji, od kodu HTML, przez zapytania SQL po kod PHP. Poniżej przedstawiam zwięzłe podsumowanie dostępnych możliwości.

UTF-8 na poziomie PHP

Obsługa UTF-8 w podstawowych operacjach łańcuchowych, takich jak konkatenacja i przypisywanie łańcuchów do zmiennych, nie wymaga żadnych specjalnych zabiegów. Natomiast większość funkcji łańcuchowych, np. strpos() i strlen(), wymaga specjalnych środków. Wiele z tych funkcji ma odpowiedniki z przedrostkiem mb_* w nazwie, np. mb_strpos() i mb_strlen(). Zbiorczo te dodatkowe funkcje nazywają się funkcjami do pracy z łańcuchami wielobajtowymi (ang. Multibyte String Functions). Funkcje te służą do pracy właśnie z łańcuchami zakodowanymi w standardzie Unicode.

Funkcji z przedrostkiem mb_* w nazwie musisz używać, jeśli pracujesz z łańcuchami Unicode. Na przykład, jeżeli użyjesz funkcji substr() z łańcuchem UTF-8, to ponosisz spore ryzyko, że w wyniku otrzymasz nieskładną mieszaninę niezrozumiałych znaków. W takim przypadku należałoby użyć funkcji mb_substr().

Najtrudniej jest zapamiętać, że funkcji z przyrostkiem mb_* w nazwie powinno się używać cały czas. Jeśli zapomniesz o tym choćby w jednym miejscu, to łańcuch Unicode może w efekcie przetwarzania zamienić się w niezrozumiałą mieszaninę znaków.

Nie wszystkie funkcje łańcuchowe mają odpowiednik z przedrostkiem mb_* w nazwie. Jeśli nie znajdziesz takiej funkcji, a będzie Ci ona potrzebna, to po prostu masz pecha.

Ponadto, na początku każdego skryptu PHP (albo na początku globalnego skryptu, do którego dołączane są inne skrypty) powinno się wywoływać funkcję mb_internal_encoding(), a jeśli skrypt wysyła dane do przeglądarki, to za nią należy jeszcze wywoływać funkcję mb_http_output(). Takie bezpośrednie definiowanie kodowania łańcuchów pozwoli ci uniknąć wielu kłopotów.

Dodatkowo wiele funkcji PHP działających na łańcuchach przyjmuje opcjonalny parametr służący do określania kodowania znaków. Jeśli jest taka możliwość, zawsze powinno się podawać UTF-8. Na przykład, funkcja htmlentities() ma opcję określenia kodowania znaków i zawsze powinno się określać UTF-8.

UTF-8 na poziomie MySQL

Jeśli skrypt PHP łączy się z bazą danych MySQL, to istnieje ryzyko, że w bazie danych łańcuchy zostaną zapisane w postaci innej niż UTF-8, nawet jeśli uwzględnisz wszystkie opisane wyżej środki bezpieczeństwa.

Aby mieć pewność, że PHP przekaże do MySQL łańcuchy w formacie UTF-8, baza danych musi mieć ustawione kodowanie znaków i kolacjonowanie na utf8mb oraz należy używać zestawu znaków utf8mb4 w łańcuchach połączenia PDO. Więcej informacji na ten temat znajdziesz w artykule o nawiązywaniu połączeń z bazą danych MySQL. Jest to bardzo ważne.

Pamiętaj że jeśli potrzebujesz kompletnej obsługi standardu UTF-8, musisz użyć zestawu znaków utf8mb4, nie utf8!

UTF-8 na poziomie przeglądarki

Aby skrypty PHP wysyłały do przeglądarki łańcuchy UTF-8, należy posługiwać się funkcją mb_http_output(). Ponadto w nagłówku HTML head dodatkowo umieść odpowiedni znacznik charset.

Przykład

<?php
// Informujemy PHP, że do końca skryptu używamy łańcuchów UTF-8.
mb_internal_encoding('UTF-8');
 
// Informujemy PHP, że będziemy wysyłać do przeglądarki dane w formacie UTF-8.
mb_http_output('UTF-8');
 
// Testowy łańcuch w formacie UTF-8.
$string = 'Êl síla erin lû e-govaned vîn.';
 
// Przekształcamy nasz łańcuch za pomocą funkcji wielobajtowej.
$string = mb_substr($string, 0, 15);
 
// Łączymy się z bazą danych w celu zapisania w niej przekształconego łańcucha.
// Więcej informacji na ten temat znajduje się w artykule o PDO.
// Zwróć uwagę na definicję zestawu znaków utf8mb4 w łańcuchu połączenia PDO.
$link = new \PDO(   'mysql:host=your-hostname;dbname=baza-danych;charset=utf8mb4',
                    'nazwa-użytkownika',
                    'hasło',
                    array(
                        \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
                        \PDO::ATTR_PERSISTENT => false
                    )
                );
 
// Zapisujemy przekształcony łańcuch w formacie UTF-8 w bazie danych.
// Baza danych i jej tabele mają ustawiony zestaw znaków i kolacjonowanie na utf8mb4, prawda?
$handle = $link->prepare('insert into ElvishSentences (Id, Body) values (?, ?)');
$handle->bindValue(1, 1, PDO::PARAM_INT);
$handle->bindValue(2, $string);
$handle->execute();
 
// Pobieramy wcześniej zapisany łańcuch, aby sprawdzić czy został zapisany prawidłowo.
$handle = $link->prepare('select * from ElvishSentences where Id = ?');
$handle->bindValue(1, 1, PDO::PARAM_INT);
$handle->execute();
 
// Zapisujemy wynik w obiekcie, który później przedstawimy w kodzie HTML.
$result = $handle->fetchAll(\PDO::FETCH_OBJ);
?><!doctype html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Testowa strona UTF-8</title>
    </head>
    <body>
        <?php
        foreach($result as $row){
            print($row->Body);  // Ta instrukcja powinna przekazać nasz przekształcony łańcuch UTF-8 przeglądarce.
        }
        ?>
    </body>
</html>

Autor: Alex Cabal

Źródło: http://phpbestpractices.org/

Tłumaczenie: Łukasz Piwko

Treść tej strony jest dostępna na zasadach licencji CC BY-SA 3.0