Почему ваша кириллица превращается в "Привет" и как это исправить раз и навсегда
Вы когда-нибудь открывали свой сайт на PHP и вместо привычного "Привет, мир!" видели нечто вроде Привет, РјРёСЂ!? Если да — поздравляю, вы столкнулись с классической проблемой UTF-8 all the way through. Это та самая ситуация, когда кодировка ломается где-то на пути от базы данных до браузера, и виноваты в этом все, но никто не хочет брать на себя ответственность.
На Stack Overflow этот вопрос собрал тысячи просмотров и сотни ответов. И неспроста. Даже опытные разработчики иногда путаются в настройках кодировки. В этом туториале мы разберем пошаговый рецепт, как подружить PHP, MySQL и браузер, чтобы UTF-8 работал везде и всегда. Никакой магии — только правильные настройки.
Шаг 1. База данных: фундамент вашей кодировки
Всё начинается с базы. Если у вас в MySQL хранятся данные в latin1 или cp1251, а вы пытаетесь вывести их в UTF-8 — получите крякозябры. Но даже если база в UTF-8, есть нюанс: старая кодировка utf8 в MySQL не поддерживает символы эмодзи и некоторые редкие иероглифы. Решение — utf8mb4.
1.1. Создание таблиц с правильной кодировкой
При создании новой таблицы всегда указывайте:
CREATE TABLE articles (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
Альтернатива — задать кодировку по умолчанию для всей базы данных:
ALTER DATABASE your_database_name CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
Важно: utf8mb4_unicode_ci — это collation (правило сравнения). Оно корректно обрабатывает все символы Юникода. Если вам не нужна сортировка по разным языкам, подойдёт и utf8mb4_general_ci.
1.2. Конвертация существующих таблиц
Если у вас уже есть данные в другой кодировке, сначала сконвертируйте их:
ALTER TABLE articles CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
Но будьте осторожны: если в таблице уже были данные в latin1, они могут превратиться в двойную кодировку (например, Привет). В таком случае нужно сначала перевести столбцы в binary, а потом в utf8mb4. Это отдельная история, но для простоты считаем, что вы начинаете с чистого листа.
Шаг 2. PHP-соединение с базой: настройка PDO
В современном PHP вы используете PDO (или mysqli). И здесь есть критический момент: после подключения к базе нужно явно указать кодировку. Иначе PHP будет общаться с MySQL в кодировке по умолчанию (часто latin1).
2.1. Правильная строка подключения PDO
$host = 'localhost';
$dbname = 'your_database';
$username = 'root';
$password = '';
$dsn = "mysql:host=$host;dbname=$dbname;charset=utf8mb4";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $username, $password, $options);
} catch (PDOException $e) {
echo "Connection failed: " . $e->getMessage();
}
Обратите внимание на charset=utf8mb4 в DSN. Это заставляет PHP и MySQL договариваться о кодировке на уровне соединения.
2.2. Альтернативный способ (если не работает DSN)
Иногда старые версии PHP или серверов не считывают charset из DSN. Тогда добавьте после подключения:
$pdo->exec("SET NAMES 'utf8mb4'");
Но первый способ предпочтительнее — он чище и современнее.
Шаг 3. HTTP-заголовки и HTML: говорим браузеру правду
Даже если база и PHP работают в UTF-8, браузер может решить, что вы отдаёте ему windows-1251. И тогда он попытается интерпретировать UTF-8 байты как однобайтовую кодировку — получите кракозябры.
3.1. HTTP-заголовок Content-Type
Самый надёжный способ — отправить заголовок через PHP ДО любого вывода:
header('Content-Type: text/html; charset=utf-8');
Это должно быть в самом начале скрипта, до <html> или любого echo.
3.2. Мета-тег в HTML
На случай, если заголовок не сработал (например, при статическом HTML), продублируйте в <head>:
<meta charset="UTF-8">
Или старый вариант (но лучше используйте первый):
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
Важно: Если вы используете фреймворки (Laravel, Symfony), они обычно сами устанавливают заголовки. Но проверьте — иногда нужно явно указать 'charset' => 'utf-8' в конфиге.
Шаг 4. Работа с многобайтовыми строками в PHP
Стандартные PHP-функции вроде strlen(), substr() или strpos() работают с байтами, а не с символами. Для кириллицы (3 байта на символ в UTF-8) это приводит к ошибкам. Например, substr("Привет", 0, 3) вернёт "Пр" (первые 3 байта), а не "При". Решение — использовать mb_* функции.
4.1. Включаем mb_string
Убедитесь, что расширение mb_string включено в php.ini. Затем используйте:
// Вместо strlen
echo mb_strlen("Привет", 'UTF-8'); // 6
// Вместо substr
echo mb_substr("Привет мир", 0, 6, 'UTF-8'); // Привет
// Вместо strpos
echo mb_strpos("Привет мир", "мир", 0, 'UTF-8'); //