Александр Некрасов · Автономное руководство

PHP: полный мануал с примерами, функциями и ООП

Разделы этой страницы
  1. Iconv: кодировки, символы и MIME-заголовки
  2. Импорт товара: увидеть буквы вместо чужих байтов
  3. Установка: расширение PHP и системная библиотека — два слоя
  4. Четыре константы: диагностика реализации и флаги MIME
  5. Байт, кодовая точка, графема: выбрать правильную единицу
  6. Ошибка источника, узкий целевой алфавит и опасное «исправление»
  7. Три настройки и их приоритет: не превращать глобальное состояние в магию
  8. BOM и большой файл: обработать границу, а не менять всё подряд
  9. Как выбрать функцию и пройти практику дальше
  10. Функции и методы этой темы
  11. Код и руководство с собой
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 94 · От основ к практике

Iconv: кодировки, символы и MIME-заголовки

Полный раздел Iconv: известные байты → читаемый текст, 11 документированных API-страниц, все настройки/константы, потери и ошибки.

Импорт товара: увидеть буквы вместо чужих байтов

Наглядная модель

Поменять кодировку байтов, сохранив текст

Старая выгрузка партнёра содержит слово Чай в известной Windows-1251. Сайт хранит текст в UTF-8.

  1. Вход Windows-1251

    Исходная кодировка известна из договора обмена, а не угадана по имени файла.

  2. iconv

    Декодирует исходные байты и кодирует те же символы в UTF-8.

  3. Новые байты UTF-8

    Слово остаётся Чай, но его бинарное представление и длина в байтах меняются.

  4. Проверка результата

    Отказ или потеря символов должны быть обработаны явно, до сохранения выгрузки.

Строка PHP не хранит автоматически название кодировки. TRANSLIT и IGNORE могут менять или терять данные; это отдельная политика приложения.

Вы получили выгрузку от старой учётной системы. В её договоре сказано: текст Windows-1251. Ваш сайт работает в UTF-8. Задача — перевести байты один раз на входе, затем обрабатывать обычный UTF-8. Iconv — семейство функций для такого преобразования, подсчёта/поиска символов в выбранной кодировке и работы с MIME-заголовками.

У одного слова могут быть разные байты
  1. Система поставщика: d7 e0 e9
  2. Договор сообщает Windows-1251
  3. iconv читает буквы Ч а й и записывает их в UTF-8
  4. Приложение получает d0 a7 d0 b0 d0 b9 → Чай

PHP-строка хранит байты, а не прикреплённый к ним ярлык кодировки. Запись $raw называет переменную; iconv(...) вызывает функцию; три значения в скобках — её аргументы; = сохраняет возвращённую строку. Запись "\xD7" задаёт конкретный байт, чтобы опыт не зависел от кодировки файла редактора.

Первый результат: прочитать название товараСамостоятельный пример
<?php
declare(strict_types=1);

$raw = "\xD7\xE0\xE9";
$name = iconv('Windows-1251', 'UTF-8', $raw);
if ($name === false) { echo "Выгрузка не прочитана\n"; }
else { echo 'Товар: ', $name, "\n"; }
Ожидаемый результат
Товар: Чай

В примере кодировка известна заранее. Проверка === false нужна, потому что iconv может отказать; она не путает отказ с пустым текстом. Здесь нет файла, формы, HTTP или БД — только видимая проверка преобразования.

Кодировка — договор о том, какие байты означают какие символы. UTF-8 умеет представлять весь Unicode; старые однобайтные таблицы имеют более узкий набор. Обратное преобразование в Windows-1251 может отказать на emoji даже при корректном исходном UTF-8. Не подменяйте это «починкой» текста удалением неподходящих букв.

Если сайт, API, БД и файлы уже согласованно используют UTF-8, дополнительный iconv на каждом шаге обычно не нужен. Храните один внутренний формат; преобразуйте только на известной внешней границе. Начальные понятия: переменная, аргументы и результат функции, условие. Подробный контракт первого вызова — iconv.

Установка: расширение PHP и системная библиотека — два слоя

Расширение iconv — интерфейс PHP к реализации Iconv, которая выполняет преобразование. Поэтому совпадение версии PHP ещё не означает одинаковые списки кодировок и правила приближённой замены. Современные POSIX-системы часто содержат iconv в libc; если её нет, используется отдельная библиотека, например GNU libiconv. На Windows поддержка определяется составом выбранной сборки.

Что проверяется перед импортом
  1. Нужный PHP/SAPI запущен
  2. extension_loaded("iconv") подтверждает расширение
  3. ICONV_IMPL / ICONV_VERSION показывают backend
  4. Пробная конвертация проверяет именно необходимую пару
Официальный шаг setupПрактическое значение
RequirementsТребуется реализация Iconv: системная либо внешняя. GNU libiconv — один вариант, не единственное допустимое окружение.
InstallationПри сборке PHP расширение включено по умолчанию. --without-iconv отключает, --with-iconv[=DIR] выбирает поддержку/расположение внешней библиотеки. Это configure-параметры сборки, не значения php.ini.
Готовые сборки/пакетыПроверьте документацию вашего поставщика PHP и фактическую availability. Не ищите произвольную iconv.dll и не добавляйте extension=iconv вслепую: расширение может быть встроено.
CLI и webКоманда php может запускать другую версию/ini, чем FPM, Apache или IIS. Проверять надо тот процесс, где выполняется импорт. Закрытую диагностику после проверки убирают.
Имена кодировокЗависят от реализации. Например, официальный Manual отдельно оговаривает ISO8859-1 вместо ISO-8859-1 на некоторых IBM AIX. Проверяйте реальные имена своей системы.
Убедиться, что нужный простой обмен доступенСамостоятельный пример
<?php
declare(strict_types=1);

var_dump(extension_loaded('iconv'));
var_dump(defined('ICONV_IMPL'), defined('ICONV_VERSION'));
$converted = iconv('Windows-1251', 'UTF-8', "\xD7\xE0\xE9");
echo $converted === 'Чай' ? "Учебная пара кодировок работает\n" : "Пара не прошла проверку\n";
Ожидаемый результат
bool(true)
bool(true)
bool(true)
Учебная пара кодировок работает

Успех подтверждает данную пару и данные в этой среде. В проверенном PHP 8.5.11 используется libiconv 1.19. На другой машине сначала проверьте availability, затем переходите к вызовам; не публикуйте подробности окружения в открытом endpoint.

У PHP Iconv нет универсальной iconv_list() для всех имён. mb_list_encodings относится к другому backend и не подтверждает поддержку тех же псевдонимов в Iconv. Консольная программа iconv, если установлена, также может оказаться другой реализацией, чем PHP-расширение. Не делайте заключение только по команде из shell.

Вся эта глава проверена на одной сборке PHP 8.5.11 с project ini и libiconv 1.19. Платформенные различия подтверждаются документацией; запусков на glibc/AIX здесь не заявлено.

Четыре константы: диагностика реализации и флаги MIME

Константа читается без знака $, например ICONV_MIME_DECODE_STRICT. Здесь две константы сообщают, чем работает расширение, а две выбирают режим MIME-декодера. Первые не делают преобразований, вторые не относятся к обычному iconv.

КонстантаКонтракт
ICONV_IMPL (string)Название реализации Iconv; формат значения зависит от сборки. Не разбирать его как обязательное фиксированное слово.
ICONV_VERSION (string)Версия используемой реализации, не PHP и не поддерживаемого Unicode.
ICONV_MIME_DECODE_STRICT (int, 1)Строгий разбор MIME encoded-word по RFC 2047; только режим mime_decode / mime_decode_headers.
ICONV_MIME_DECODE_CONTINUE_ON_ERROR (int, 2)Продолжать разбор при ошибке; возможны необработанные фрагменты. Объединяется со STRICT через |.
Собрать режим из понятных имёнСамостоятельный пример
<?php
declare(strict_types=1);

$mode = ICONV_MIME_DECODE_STRICT | ICONV_MIME_DECODE_CONTINUE_ON_ERROR;
echo $mode, "\n";
echo iconv_mime_decode('=?UTF-8?B?0KfQsNC5?=', $mode, 'UTF-8'), "\n";
Ожидаемый результат
3
Чай

| — побитовое ИЛ: бит 1 и бит 2 включены одновременно. Это один аргумент mode, а не два последовательных вызова. Успех на корректной теме не отменяет более мягкую политику при повреждённом входе.

Для отчёта о совместимости сохраните PHP_VERSION, ICONV_IMPL, ICONV_VERSION и пример конкретных входных байтов без секретов. Одна только строка версии не заменяет проверку требуемой операции. Политика continue нужна для отображения проблемной корреспонденции; бизнес-решение о приёме данных формулируется отдельно.

Байт, кодовая точка, графема: выбрать правильную единицу

Пусть поле формы ограничено десятью символами, экспорт — двадцатью байтами, а карточка — шестью видимыми знаками. Эти три требования различны. В UTF-8 символ может занимать несколько байтов, а видимый знак — несколько кодовых точек. Iconv умеет считать и выделять символы кодировки, но не выполняет весь набор правил отображения текста.

Один видимый знак é в разложенной форме
  1. e занимает 1 UTF-8 байт
  2. Отдельный акцент U+0301 занимает 2 байта
  3. Вместе: strlen=3, iconv_strlen=2
  4. Одна воспринимаемая графема требует инструмента Intl
ТребованиеПодход
Лимит сетевого пакетаstrlen по байтам; не считать число символов достаточным.
Количество/позиция символовiconv_strlen / iconv_strpos / iconv_substr с явной кодировкой.
Превью без разрыва графемыIntl grapheme_*; для emoji/комбинируемых букв одного iconv_substr недостаточно.
Одинаковость составного и разложенного текстаUnicode-нормализация по согласованной политике; iconv не решает её автоматически.
Подстрока ASCII-протокола по байтовому offsetОбычный substr/strpos уместен. Главное — не передавать байтовый индекс в символьный API.

Например, UTF-8 «Чай» занимает 6 байт и 3 символа. substr($name,0,1) оставит половину первой кириллической буквы; последующий iconv справедливо сообщит о повреждении. Исследуйте происхождение байтов, а не подавляйте ошибку конвертера. Подробнее: iconv_strlen, iconv_substr, iconv_strpos, iconv_strrpos.

Ошибка источника, узкий целевой алфавит и опасное «исправление»

Сбой конвертации не всегда означает испорченный файл. Есть как минимум три причины: неверно названа исходная кодировка; входная последовательность действительно повреждена; правильный символ не представим в целевой кодировке. Сначала установите причину, затем выбирайте политику обработки. Переименовать charset или дописать IGNORE — не универсальное решение.

Диагностика одной границы
  1. Сохраняем исходные байты и договор источника
  2. Проверяем, что API вызван с нужными именами
  3. Отдельно проверяем целевую представимость
  4. При отказе сохраняем безопасный ID партии и причину
  5. Повторяем импорт после исправления источника/договора
СитуацияРазумное действие
Повреждён UTF-8Отклонить/изолировать запись; не превращать незаметно AB и A[ошибочный байт]B в одинаковое значение.
Emoji при экспорте в Windows-1251Согласовать UTF-8 или явное правило потери для конкретного неключевого поля. Не терять символ молча.
Неизвестное имя charsetИсправить договор/конфигурацию; кодировку нельзя выбирать из любого пользовательского ввода.
Буквы уже испорчены двойной конвертациейНайти место первого неверного декодирования. Обратимость не гарантируется, если там уже появились ? или удалённые байты.
Notice превращён в исключениеУчитывать установленный обработчик ошибок/фреймворк. false и диагностический канал — два аспекта отказа.
Необходим preview повреждённого письмаContinue MIME может сохранить часть исходной оболочки; показывать с HTML-экранированием и пометкой, не объявлять валидным бизнес-вводом.

UTF-8-валидность и целевая представимость — разные проверки. mb_check_encoding может подтвердить корректность UTF-8, но не возможность записать emoji в UCS-2 или кириллицу в ASCII. Переносимые правила должны обходиться без зависимости от конкретной транслитерации; она может различаться между системными реализациями/окружениями.

В учебных отказах предупреждение перехватывается точечно и handler восстанавливается в finally. В рабочем коде не отключайте глобально E_WARNING/E_NOTICE ради чистого экрана: серверный журнал и понятная ошибка пользователю решают разные задачи. Не пишите весь импорт, пароль, токен или заголовок письма в диагностику. Смотрите контракт обработчиков ошибок и безопасного журнала.

Три настройки и их приоритет: не превращать глобальное состояние в магию

У расширения ровно три специальные ini-директивы. Все по умолчанию пусты, имеют режим INI_ALL и deprecated с PHP 5.6. INI_ALL означает доступность изменения на разных уровнях конфигурации PHP, а не обещание, что любой конкретный хостинг разрешит изменение во всех файлах.

ДирективаНазначение и современная альтернатива
iconv.input_encodingПустая строка по умолчанию. Старое значение входной кодировки; Manual рекомендует оставить пустым и использовать Core input_encoding.
iconv.output_encodingПустая строка по умолчанию. Цель output handler; вместо legacy override Core output_encoding.
iconv.internal_encodingПустая строка по умолчанию. Источник/значение по умолчанию для операций. Manual рекомендует default_charset; Core internal_encoding при явной настройке также участвует в fallback.
Фактический выбор encoding в PHP 8.5
  1. Непустой iconv.internal_encoding имеет приоритет
  2. Иначе читается непустой Core internal_encoding
  3. Затем default_charset
  4. Если всё пусто — UTF-8; явный аргумент функции предпочтительнее скрытого default

Для input/output действует аналогичная цепочка с соответствующим Core input_encoding/output_encoding. Она проверена по исходнику PHP 8.5.11; getter возвращает эффективный выбор, а ini_get конкретной директивы — её сырую строку. Все эти имена описывают настройки, они не прикрепляют метаданные к $text и не перекодируют значения автоматически.

Например, default_charset=UTF-8 не исправит Windows-1251 внутри переменной и не перекодирует сам по себе $_POST. А изменение internal_encoding внутри валидатора может повлиять на другую библиотеку в том же процессе. В долгоживущем worker состояние сохраняется между заданиями, пока приложение не восстановит его. Передача UTF-8 аргументом обычной текстовой операции гораздо понятнее такой зависимости.

Полные контракты: iconv_get_encoding читает; iconv_set_encoding показывает legacy-запись; ob_iconv_handler применяет конвертацию только на ступени вывода. Копировать старые образцы PHP.net с setter в новый код без учёта deprecated не следует.

BOM и большой файл: обработать границу, а не менять всё подряд

Иногда договор поставщика явно допускает UTF-8 BOM в начале файла. Это байты EF BB BF, обозначающие U+FEFF, а не часть заголовка CSV. Удалять их стоит только в известном месте для известного формата. str_replace по всей строке может изменить содержимое поля и уже не является обработкой сигнатуры.

Убрать только известный начальный UTF-8 BOMСамостоятельный пример
<?php
declare(strict_types=1);

$csv = "\xEF\xBB\xBFname;qty\nЧай;2\n";
if (str_starts_with($csv, "\xEF\xBB\xBF")) { $csv = substr($csv, 3); }
$text = iconv('UTF-8', 'UTF-8', $csv);
if ($text === false) { throw new RuntimeException('Не UTF-8'); }
echo $text;
Ожидаемый результат
name;qty
Чай;2

Три байта удалены по точной проверенной сигнатуре, поэтому обычный substr здесь уместен. Это не определитель любой кодировки и не удаление всех BOM/невидимых символов. Для UTF-16 нужны свой договор и отдельное преобразование.

Для маленькой ограниченной выгрузки удобна одна конвертация строки. Большой файл нельзя безопасно «ускорить», читая произвольные 1024 байта и каждый раз вызывая iconv: многобайтный символ может попасть на границу порций. Потоковый фильтр convert.iconv.* предназначен для работы в системе streams; его создание, предупреждения, завершение и неполная последняя последовательность тоже требуют проверки.

Прочитать UTF-16LE через потоковый фильтрСамостоятельный пример
<?php
declare(strict_types=1);

$stream = fopen('php://temp', 'w+b');
if ($stream === false) { throw new RuntimeException('Не создан поток'); }
try {
    fwrite($stream, "\x27\x04\x30\x04\x39\x04"); // Чай в UTF-16LE, без BOM.
    rewind($stream);
    $filter = stream_filter_append($stream, 'convert.iconv.UTF-16LE/UTF-8', STREAM_FILTER_READ);
    if ($filter === false) { throw new RuntimeException('Нет фильтра'); }
    $text = stream_get_contents($stream);
    if ($text === false) { throw new RuntimeException('Ошибка чтения'); }
    echo $text, "\n";
} finally { fclose($stream); }
Ожидаемый результат
Чай

Опыт демонстрирует подключение фильтра, не заявляет ограниченную память всей программы: stream_get_contents здесь всё равно собирает конечную строку. Для большого файла потребителю надо постепенно обрабатывать результат; строки CSV могут содержать переносы внутри кавычек, поэтому простой fgets не заменяет полноценный CSV-парсер.

Исходные данные в примере записаны в временный поток и закрываются; внешних файлов и сети нет. Полный API streams — отдельная категория справочника; эта дополнительная иллюстрация не заменяет его контракт. Проверяйте ошибки и после чтения, не только успешное создание фильтра, особенно если вход завершается половиной символа.

Как выбрать функцию и пройти практику дальше

ПотребностьКуда перейти
Прочитать файл в известной старой кодировкеiconv
Посчитать символы / сделать превьюiconv_strlen · iconv_substr
Найти первое / последнее совпадениеiconv_strpos · iconv_strrpos
Сформировать тему письмаiconv_mime_encode
Прочитать одно поле / блок заголовковiconv_mime_decode · iconv_mime_decode_headers
Разобраться с defaults старого проектаiconv_get_encoding · iconv_set_encoding
Понять конвертацию всего output bufferob_iconv_handler

Практический маршрут: получите вымышленную Windows-1251 строку товара → переведите в UTF-8 → проверьте символьный/байтовый лимит → подготовьте короткое превью → отдельно сделайте MIME-тему уведомления. Кодировка не должна решать остальные задачи формы, CSV или отправки письма. На каждой границе остаётся свой контракт.

Соединить импорт, проверку длины и тему уведомленияСамостоятельный пример
<?php
declare(strict_types=1);

$rawName = "\xD7\xE0\xE9";
$name = iconv('Windows-1251', 'UTF-8', $rawName);
if ($name === false) { throw new RuntimeException('Ошибка импорта'); }
$length = iconv_strlen($name, 'UTF-8');
if ($length === false || $length === 0 || $length > 40) {
    throw new RuntimeException('Недопустимая длина названия');
}
if (str_contains($name, "\r") || str_contains($name, "\n")) {
    throw new RuntimeException('Название не должно создавать новую строку темы');
}
$subject = iconv_mime_encode('Subject', 'Заказ: ' . $name,
    ['input-charset' => 'UTF-8', 'output-charset' => 'UTF-8']);
if ($subject === false) { throw new RuntimeException('Тема не подготовлена'); }
echo iconv_mime_decode($subject, ICONV_MIME_DECODE_STRICT, 'UTF-8'), "\n";
Ожидаемый результат
Subject: Заказ: Чай

Это предложенное решение мини-задачи, а не отправка письма. Попробуйте пустое название, строку длиннее 40 символов и вход с CR/LF: каждому отказу соответствует свой этап. Для проверки повреждённого UTF-8 смените договор источника явно; Windows-1251 и UTF-8 имеют разные допустимые последовательности.

Дальнейшие темы: расширенные многобайтные операции, JSON-интеграция и структура ответа, полный маршрут безопасной формы. Текст в HTML дополнительно экранируется, а подготовленная MIME-тема передаётся почтовому компоненту по его собственному контракту.

Функции и методы этой темы

Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.

iconvПеревести текст между двумя известными кодировками и явно обработать невозможность перевода.

PHP 4.0.5+ · iconv

iconv(string $from_encoding, string $to_encoding, string $string): string|false
Параметр / результатЧто передать и проверить
from_encodingНазвание реальной кодировки входных байтов; автоматического определения нет. Набор имён определяется системной библиотекой.
to_encodingЦелевая кодировка; поддерживаемые //TRANSLIT и //IGNORE добавляются здесь.
stringВходная PHP-строка, в том числе бинарные байты/NUL. Не передавайте уже ошибочно перекодированную строку как исходник.
ВозвратНовая строка, включая ""; false при ошибке. Исходная строка не изменяется.
ВерсииAPI с PHP 4.0.5. Поведение преобразования зависит также от реализации Iconv, не только от версии PHP.
Изменяются байты, сохраняется смысл
  1. Windows-1251: d7 e0 e9
  2. iconv знает исходную таблицу и целевую UTF-8
  3. UTF-8: d0 a7 d0 b0 d0 b9 → Чай

Поставщик прислал название товара в Windows-1251, а приложение хранит текст в UTF-8. Первые два аргумента описывают старую и новую кодировки; третий содержит исходные байты. Функция возвращает новую строку и не меняет переменную с исходником. Слово «исходная» здесь принципиально: PHP не угадывает кодировку по внешнему виду букв.

Превратить известные байты названия в UTF-8Самостоятельный пример
<?php
declare(strict_types=1);

$supplierBytes = "\xD7\xE0\xE9";
$name = iconv('Windows-1251', 'UTF-8', $supplierBytes);
if ($name === false) { throw new RuntimeException('Не удалось прочитать название'); }
echo $name, "\n";
echo bin2hex($supplierBytes), ' -> ', bin2hex($name), "\n";
Ожидаемый результат
Чай
d7e0e9 -> d0a7d0b0d0b9

Слева три байта старого формата, справа шесть байт UTF-8. bin2hex только показывает байты; само преобразование делает iconv. Проверка === false отделяет ошибку от допустимых пустой строки и строки "0".

Экспортировать строку заказа в старую учётную системуСамостоятельный пример
<?php
declare(strict_types=1);

$line = "SKU-7;Чай;2\r\n";
$export = iconv('UTF-8', 'Windows-1251', $line);
if ($export === false) { throw new RuntimeException('Строка не представима в формате обмена'); }
echo bin2hex($export), "\n";
echo iconv('Windows-1251', 'UTF-8', $export);
Ожидаемый результат
534b552d373bd7e0e93b320d0a
SKU-7;Чай;2

Экспорт пока остаётся переменной, запись файла или отправка идут отдельным шагом. Обратное преобразование подтверждает конкретную строку примера, но не доказывает, что выбранная исходная кодировка верна для любого файла поставщика.

Если целевой алфавит не содержит символа, обычное преобразование отказывает. Суффикс //TRANSLIT у целевой кодировки просит приблизительную замену, иногда несколькими символами. //IGNORE разрешает отбросить непредставимые знаки. Это осознанная потеря данных, а не лечение неизвестной кодировки: для фамилии, реквизитов, артикула, подписи или пароля такое правило обычно недопустимо.

Увидеть разницу удаления и приближенияСамостоятельный пример
<?php
declare(strict_types=1);

$priceLabel = 'A€B';
echo iconv('UTF-8', 'ASCII//IGNORE', $priceLabel), "\n";
echo iconv('UTF-8', 'ASCII//TRANSLIT', $priceLabel), "\n";
Ожидаемый результат
AB
AEURB

Точный результат TRANSLIT проверен на libiconv 1.19 в данной PHP 8.5.11. Другой backend/окружение может дать иную замену или отказ. Нельзя строить переносимый идентификатор или правило равенства людей на таком результате.

Отклонить испорченный UTF-8, сохранив причину отказаСамостоятельный пример
<?php
declare(strict_types=1);

$diagnostic = null;
set_error_handler(static function (int $level, string $message) use (&$diagnostic): bool {
    $diagnostic = $level; // Только тип ошибки, без содержимого входной строки.
    return true;
}, E_NOTICE | E_WARNING);
try { $text = iconv('UTF-8', 'UTF-8', "A\xFFB"); }
finally { restore_error_handler(); }
echo $text === false && $diagnostic !== null ? "Импорт отклонён\n" : "Принят\n";
Ожидаемый результат
Импорт отклонён

Обработчик ограничен одним вызовом и обязательно снят. В приложении запишите безопасный ID пакета и причину, а не весь текст клиента. Фреймворк может преобразовать notice в ErrorException: тогда учитывается его политика ошибок, а не добавляется @.

Неверное/незавершённое многобайтное слово и непредставимый символ обычно дают E_NOTICE и false; неизвестная пара кодировок — E_WARNING и false. Точные сообщения/поддержка суффиксов зависят от backend. Значения неверного PHP-типа в строгом вызове дают TypeError. Имена кодировок задавайте из доверенного договора/списка: utf8mb4 — имя MySQL, для iconv используйте UTF-8. PHP 8.5 также отклоняет чрезмерно длинные имена кодировок предупреждением и false.

Все байты входа и результата находятся в памяти одновременно; для больших файлов нужен продуманный потоковый конвейер, а не чтение всего файла. Конвертация не экранирует HTML, не нормализует Unicode, не проверяет содержание CSV/JSON и не гарантирует отсутствие управляющих символов. Альтернативы: mb_convert_encoding с его отдельными правилами подстановки; mb_check_encoding для проверки заявленной кодировки; нормализация и графемы относятся к Intl.

Рядом: iconv_strlen, iconv_substr, iconv_get_encoding.

Первый опыт с байтами и кодировкой · Ошибки, потери и границы гарантий.

iconv_strlenИзмерить текст в символах выбранной кодировки, отделив их от байтов и графем.

PHP 5+ · iconv

iconv_strlen(string $string, ?string $encoding = null): int|false
Параметр / результатЧто передать и проверить
stringСтрока в заявленной кодировке.
encodingЯвное имя или null → эффективная internal_encoding. Nullable с PHP 8.0.
ВозвратКоличество символов, 0 для пустой строки; false при ошибке кодировки.
ОшибкиNotice/warning и false при ошибке конвертера; TypeError при неподходящем PHP-типе в strict_types.
Одна надпись, два измерения
  1. Чай в UTF-8 занимает 6 байт
  2. strlen → 6
  3. iconv_strlen с UTF-8 → 3
  4. Видимые графемы — отдельная модель

Ограничение названия товара «до 40 символов» нельзя проверять strlen: кириллица в UTF-8 занимает несколько байт на букву. iconv_strlen считает символы указанной кодировки. Это всё ещё не число видимых знаков: буква с отдельным ударением/акцентом состоит из нескольких кодовых точек.

Разделить байты и символыСамостоятельный пример
<?php
declare(strict_types=1);

$name = 'Чай';
echo strlen($name), " bytes\n";
echo iconv_strlen($name, 'UTF-8'), " characters\n";
echo iconv_strlen("e\u{0301}", 'UTF-8'), " code points\n";
Ожидаемый результат
6 bytes
3 characters
2 code points

Комбинирующий акцент U+0301 здесь указан явно. Если продукт считает то, что человек воспринимает одной буквой, нужен grapheme_strlen из Intl, а не замена лимита байтов лимитом кодовых точек.

Проверить две разные границы карточки товараСамостоятельный пример
<?php
declare(strict_types=1);

$name = 'Чай с мятой';
$length = iconv_strlen($name, 'UTF-8');
if ($length === false) { throw new RuntimeException('Неверная кодировка'); }
$accepted = $length <= 20 && strlen($name) <= 64;
echo $accepted ? "Название подходит\n" : "Слишком длинно\n";
var_dump(iconv_strlen('', 'UTF-8'));
Ожидаемый результат
Название подходит
int(0)

20 — правило продукта по символам, 64 — условный байтовый бюджет внешнего протокола; это разные ограничения. Пустая строка допустима для функции, но форма отдельно может требовать непустое название.

Значение encoding=null/пропуск берётся из эффективной внутренней настройки Iconv; nullable появился в PHP 8.0. Передача UTF-8 делает библиотечную функцию независимой от чужих ini_set. Невалидная последовательность или неизвестная кодировка дают false и диагностическое сообщение. Подсчёт проходит строку: при проверке нескольких условий сохраните результат, не считайте заново в каждой итерации.

Рядом: iconv_substr, mb_strlen.

Первый опыт с байтами и кодировкой · Ошибки, потери и границы гарантий.

iconv_strposНайти первое совпадение и получить позицию в символах, включая корректный результат 0.

PHP 5+ · iconv

iconv_strpos(string $haystack, string $needle, int $offset = 0, ?string $encoding = null): int|false
Параметр / результатЧто передать и проверить
haystack / needleГде искать / что искать; обе строки в одной encoding, регистр учитывается.
offsetСтарт в символах. 0 — начало; отрицательные с PHP 7.1 отсчитываются от конца.
encodingnull/пропуск → внутреннее значение; nullable с PHP 8.0.
ВозвратИндекс первого совпадения от начала всей строки, либо false. Пустая needle даёт false в PHP 8.5.11.
ОшибкиPHP 8.5: ValueError для offset вне допустимого диапазона; ошибка кодировки — false и warning/notice.
Поиск по символам
  1. Чай, кофе, чай
  2. кофе начинается на символе 5
  3. Возвращается 5, не число предшествующих байтов

Нужно найти первое вхождение слова в русском названии, затем работать с позицией как с номером символа. iconv_strpos возвращает такой номер, начиная с нуля. Обычный strpos возвращает байтовое смещение; смешивать эти две системы координат нельзя.

Найти слово с нулевой и ненулевой позициейСамостоятельный пример
<?php
declare(strict_types=1);

$title = 'Чай, кофе, чай';
var_dump(iconv_strpos($title, 'Чай', 0, 'UTF-8'));
var_dump(iconv_strpos($title, 'кофе', 0, 'UTF-8'));
var_dump(iconv_strpos($title, 'сок', 0, 'UTF-8'));
Ожидаемый результат
int(0)
int(5)
bool(false)

Ноль — успешный поиск в начале. Проверяйте !== false; условие if ($position) пропустит найденное первое слово. Поиск чувствителен к регистру: Чай и чай различаются.

Перейти ко второму совпадению в списке метокСамостоятельный пример
<?php
declare(strict_types=1);

$labels = 'чай|кофе|чай';
$first = iconv_strpos($labels, 'чай', 0, 'UTF-8');
if ($first === false) { throw new RuntimeException('Первой метки нет'); }
$second = iconv_strpos($labels, 'чай', $first + 1, 'UTF-8');
echo $first, '/', $second, "\n";
echo iconv_strpos($labels, 'чай', -3, 'UTF-8'), "\n";
Ожидаемый результат
0/9
9

offset — точка, с которой начинается просмотр; возвращаемая позиция всё равно отсчитывается от начала всей строки. +1 допускает перекрывающиеся совпадения; для неперекрывающихся прибавьте длину непустой искомой строки.

Отрицательный offset поддерживается с PHP 7.1 и отсчитывается от конца; в PHP 8.5 смещение за пределы длины вызывает ValueError, ровно в конце поиск непустой строки возвращает false. В отличие от современного strpos, пустая needle у iconv_strpos в проверенной PHP 8.5.11 возвращает false. Для пользовательского поиска определите отдельное правило пустого запроса. Обе строки должны быть в encoding; функция не перекодирует needle из иной кодировки и не приводит Unicode к одной форме.

Историческая фраза некоторых страниц о преобразовании числовой needle в код символа не подходит современному строгому коду: параметр имеет тип string. Передавайте строку явно; не рассчитывайте на int→символ. Ошибки входной последовательности дают false с диагностикой, отсутствие совпадения — false без ошибки. Альтернативы: mb_strpos и байтовый strpos, если ищется ASCII-разделитель, а результат используется именно как байтовая позиция.

Рядом: iconv_strrpos, iconv_substr.

Первый опыт с байтами и кодировкой · Ошибки, потери и границы гарантий.

iconv_strrposНайти последнее вхождение строки и безопасно отделить суффикс по символьной позиции.

PHP 5+ · iconv

iconv_strrpos(string $haystack, string $needle, ?string $encoding = null): int|false
Параметр / результатЧто передать и проверить
haystack / needleСтрока и искомая строка в общей кодировке.
encodingТретий аргумент — кодировка, не offset; null → internal, nullable с PHP 8.0.
ВозвратПозиция последнего совпадения в символах, начиная с 0; false при отсутствии/ошибке.
ОграниченияРегистрозависимый поиск; пустая needle даёт false в PHP 8.5.11; диагностические ошибки кодировки нужно различать с обычным отсутствием.
Последняя граница подписи
  1. чай / набор / 2026
  2. Последний разделитель начинается на 11
  3. iconv_substr(...,0,11) оставляет чай / набор

Когда в названии несколько одинаковых разделителей, iconv_strrpos выбирает последний. Здесь нет аргумента offset: третьим идёт encoding. Для отсечения суффикса найденную позицию удобно передать iconv_substr с той же кодировкой.

Найти последний разделительСамостоятельный пример
<?php
declare(strict_types=1);

$label = 'чай / набор / 2026';
var_dump(iconv_strrpos($label, ' / ', 'UTF-8'));
var_dump(iconv_strrpos('чай', 'чай', 'UTF-8'));
Ожидаемый результат
int(11)
int(0)

Результат — начало последнего совпадения, отсчитанное от начала строки. Значение 0 также является успехом.

Выделить имя товара без служебного суффиксаСамостоятельный пример
<?php
declare(strict_types=1);

$label = 'чай / набор / 2026';
$position = iconv_strrpos($label, ' / ', 'UTF-8');
$visible = $position === false ? $label : iconv_substr($label, 0, $position, 'UTF-8');
echo $visible, "\n";
var_dump(iconv_strrpos($label, 'нет', 'UTF-8'));
Ожидаемый результат
чай / набор
bool(false)

Это фиксированный формат метки, а не парсер пути/URL. При отсутствии разделителя оставляем весь текст. Оба этапа используют символы, поэтому кириллица не сдвигает границу.

encoding=null разрешён с PHP 8.0 и использует внутреннюю настройку. Пустая needle возвращает false в PHP 8.5.11; отсутствующее совпадение — тоже false. Неподходящая кодировка/последовательность сопровождает false диагностикой. Нет режима без учёта регистра, offset и Unicode-нормализации. Для другой семантики смотрите mb_strrpos; его сигнатура отличается, механически переносить аргументы нельзя.

Рядом: iconv_strpos, iconv_substr.

Первый опыт с байтами и кодировкой · Ошибки, потери и границы гарантий.

iconv_substrИзвлечь фрагмент без разрезания многобайтного символа и правильно понять отрицательные границы.

PHP 5+ · iconv

iconv_substr(string $string, int $offset, ?int $length = null, ?string $encoding = null): string|false
Параметр / результатЧто передать и проверить
stringСтрока в encoding.
offsetНулевой индекс символа; отрицательное значение — от конца. В PHP 8.5 слишком далёкие позиции ограничиваются началом/концом.
lengthnull/пропуск — остаток; положительное — максимум символов; 0 — пусто; отрицательное исключает столько символов в конце. Nullable с PHP 8.0.
encodingnull/пропуск → internal; nullable с PHP 8.0.
ВозвратФрагмент в исходной кодировке, включая ""; false и диагностика при ошибке кодировки.
Окно поверх текста
  1. Чай с мятой
  2. offset=0, length=3
  3. Окно охватывает Ч, а, й
  4. Результат Чай в той же UTF-8

Для короткого превью нужен начальный фрагмент текста без разрезания байтов кириллицы. offset задаёт первый символ, length — количество символов. Исходная строка остаётся прежней; функция возвращает новую в той же кодировке, а не обязательно в UTF-8.

Взять часть по символамСамостоятельный пример
<?php
declare(strict_types=1);

$text = 'Чай с мятой';
echo iconv_substr($text, 0, 3, 'UTF-8'), "\n";
echo iconv_substr($text, -5, null, 'UTF-8'), "\n";
echo iconv_substr('абвг', 1, -1, 'UTF-8'), "\n";
Ожидаемый результат
Чай
мятой
бв

Нулевой offset начинает с первой буквы, -5 берёт старт за пять символов до конца, null означает всё до конца. Отрицательная length отбрасывает указанное число символов с конца, а не идёт назад.

Добавить многоточие только при фактическом сокращенииСамостоятельный пример
<?php
declare(strict_types=1);

$description = 'Чай с мятой';
$limit = 5;
$length = iconv_strlen($description, 'UTF-8');
if ($length === false) { throw new RuntimeException('Неверный текст'); }
$preview = $length > $limit ? iconv_substr($description, 0, $limit, 'UTF-8') . '…' : $description;
echo $preview, "\n";
var_dump(iconv_substr('абвг', 4, null, 'UTF-8'));
var_dump(iconv_substr('абвг', 99, null, 'UTF-8'));
Ожидаемый результат
Чай с…
string(0) ""
string(0) ""

Многоточие добавляет ещё один знак к пяти выбранным. Это ограничение по кодовым точкам, не по словам/графемам: составной emoji или буква с комбинирующим знаком всё ещё может оказаться разделённой.

Нулевой length даёт пустую строку; слишком большой length ограничивается концом; слишком отрицательный offset начинается с начала. Если отрицательная length оставила конец раньше начала, получается пустой фрагмент. В PHP 7.0.11 отдельно изменилось поведение offset, равного длине; в PHP 8.0 length и encoding стали nullable. Не смешивайте байтовые позиции strpos с символьным offset этой функции.

В PHP 8.5 реализация сначала подсчитывает символы, затем извлекает диапазон: многократный iconv_substr($text,$i,1) в цикле может повторно просматривать длинную строку и давать квадратичную суммарную работу. Для краткого превью один вызов подходит; для полного разбора нужен алгоритм одного прохода/специализированный Unicode-инструмент. mb_substr — близкий по задаче API, grapheme_substr из Intl режет по графемам.

Рядом: iconv_strlen, iconv_strpos, iconv_strrpos.

Первый опыт с байтами и кодировкой · Ошибки, потери и границы гарантий.

iconv_mime_encodeПодготовить MIME-поле с не-ASCII текстом и корректными переносами, не выдавая его за готовое письмо.

PHP 5+ · iconv

iconv_mime_encode(string $field_name, string $field_value, array $options = []): string|false
Параметр / результатЧто передать и проверить
field_nameИмя поля без двоеточия, например фиксированное Subject. Не произвольное имя из запроса.
field_valueЗначение в input-charset. CR/LF пользовательского ввода отклоняются заранее.
optionsАссоциативный массив пяти ключей из таблицы ниже; регистр ключей значим.
ВозвратПолное поле "Name: ...", возможно folded; false при ошибке. Не только текст значения.
ОшибкиNotice/warning + false при ошибке преобразования/лимита строки; TypeError для неверных типов параметров.
Текст темы → поле для протокола
  1. Заказ готов в UTF-8
  2. UTF-8 байты упакованы как encoded-word B/Q
  3. Добавлены Subject: и переносы при необходимости
  4. Декодер восстанавливает Subject: Заказ готов

Письмо о заказе должно показать русскую тему даже там, где заголовки передаются в ASCII-совместимом формате. iconv_mime_encode создаёт целое поле, включая имя Subject и двоеточие. Внутри используются encoded-word по RFC 2047: метка кодировки, B/Q и закодированные байты. Это не шифрование и не защита содержимого.

Собрать и прочитать тему заказаСамостоятельный пример
<?php
declare(strict_types=1);

$field = iconv_mime_encode('Subject', 'Заказ готов', [
    'input-charset' => 'UTF-8',
    'output-charset' => 'UTF-8',
]);
if ($field === false) { throw new RuntimeException('Тема не закодирована'); }
echo $field, "\n";
echo iconv_mime_decode($field, ICONV_MIME_DECODE_STRICT, 'UTF-8'), "\n";
Ожидаемый результат
Subject: =?UTF-8?B?0JfQsNC60LDQtyDQs9C+0YLQvtCy?=
Subject: Заказ готов

Первая строка — фактическое содержимое поля для передачи: Subject — его имя, UTF-8 — кодировка исходного текста, B — упаковка байтов в Base64, заключительное ?= закрывает encoded-word. Вторая строка показывает, что прочитает почтовая программа после декодирования. Само письмо пока никуда не отправляется. Если API отправки принимает только значение темы, нельзя передать ему целое поле Subject:; иначе имя продублируется.

Ключ options (регистр важен)Значение и стандартное поведение
schemeСтрока B (base64, по умолчанию) либо Q (вариант quoted-printable для encoded-word). Для программного выбора используйте эти точные значения.
input-charsetСтрока: кодировка обоих аргументов field_name и field_value. По умолчанию эффективная iconv.internal_encoding.
output-charsetСтрока: кодировка байтов внутри encoded-word. По умолчанию тоже internal_encoding, а не output_encoding.
line-lengthЦелое, по умолчанию 76: длина строк поля при folding. Слишком малый бюджет может не вместить служебный синтаксис и дать false.
line-break-charsСтрока, по умолчанию CRLF ("\r\n"): разделитель при переносах. Интерпретируется как ASCII независимо от input-charset. Для почтового wire-формата сохраняйте CRLF.
Закодировать длинную тему с переносами и проверить её смыслСамостоятельный пример
<?php
declare(strict_types=1);

$subject = 'Чай с мятой: заказ №42';
$field = iconv_mime_encode('Subject', $subject, [
    'scheme' => 'Q', 'input-charset' => 'UTF-8', 'output-charset' => 'UTF-8',
    'line-length' => 60, 'line-break-chars' => "\r\n",
]);
if ($field === false) { throw new RuntimeException('Тема не закодирована'); }
$lines = explode("\r\n", $field);
foreach ($lines as $index => $line) {
    echo 'Строка ', $index + 1, ': ', $line, "\n";
    echo 'Байт: ', strlen($line), "\n";
}
echo 'После чтения: ', iconv_mime_decode($field, ICONV_MIME_DECODE_STRICT, 'UTF-8'), "\n";
Ожидаемый результат
Строка 1: Subject: =?UTF-8?Q?=D0=A7=D0=B0=D0=B9=20=D1=81=20=D0=BC?=
Байт: 57
Строка 2:  =?UTF-8?Q?=D1=8F=D1=82=D0=BE=D0=B9:=20=D0=B7=D0=B0=D0=BA?=
Байт: 59
Строка 3:  =?UTF-8?Q?=D0=B0=D0=B7=20=E2=84=9642?=
Байт: 39
После чтения: Subject: Чай с мятой: заказ №42

В выводе видны сами строки поля, их длины и восстановленная тема. Пробел в начале продолжения связывает его с тем же полем Subject. PHP-файл следует сохранить в UTF-8, иначе обещание input-charset не совпадёт с байтами. Лимит 60 выбран для наглядности, стандартное значение функции — 76. Точное разбиение здесь относится к проверенной сборке, а смысл темы должен сохраняться при любом корректном разбиении.

Отказать при попытке добавить новый заголовок через темуСамостоятельный пример
<?php
declare(strict_types=1);

$subject = "Готово\r\nBcc: injected@example.invalid";
if (str_contains($subject, "\r") || str_contains($subject, "\n")) {
    echo "Тема должна быть одной логической строкой\n";
} else {
    $field = iconv_mime_encode('Subject', $subject,
        ['input-charset' => 'UTF-8', 'output-charset' => 'UTF-8']);
    echo $field === false ? "Ошибка кодировки\n" : "Тема подготовлена\n";
}
Ожидаемый результат
Тема должна быть одной логической строкой

Имя поля фиксировано разработчиком, входные CR/LF отклоняются до кодирования. Разрешённые переносы создаёт сама функция. Это один слой безопасного формирования темы; адреса, вложения и полноценную отправку должен собирать почтовый компонент с отдельными правилами.

Функция возвращает false при невозможном преобразовании/неумещающемся заголовке и сопровождает отказ warning/notice. Неизвестные ключи не проверяются как схема настроек: опечатка output-encoding может просто не примениться. Реализация PHP 8.5 смотрит только первый символ scheme: B/b выбирает B, Q/q — Q, другие начала оставляют стандартное B. Даже недокументированное Qgarbage выберет Q: это не валидация полного значения options. Проверяйте собственную конфигурацию и используйте документированные типы. Неверный тип самого options в строгом вызове вызывает TypeError.

Не используйте encoded-word как универсальный формат для HTTP-заголовка, тела письма, имени файла вложения или адреса: там иные правила. Не нужно вручную base64_encode всего поля и добавлять метки — библиотека должна учитывать границы слов/строк. mb_encode_mimeheader работает с иным контрактом, включая параметры отступа; механическая замена без проверки длины/префикса опасна.

Рядом: iconv_mime_decode, iconv_mime_decode_headers.

Первый опыт с байтами и кодировкой · Ошибки, потери и границы гарантий.

iconv_mime_decodeВосстановить читаемый текст одного MIME-поля с явным выбором строгого или резервного разбора.

PHP 5+ · iconv

iconv_mime_decode(string $string, int $mode = 0, ?string $encoding = null): string|false
Параметр / результатЧто передать и проверить
stringОдно поле MIME или его значение; допускает encoded-word и folding.
mode0 либо комбинация двух флагов STRICT и CONTINUE_ON_ERROR через |.
encodingКодировка результата; null → internal, nullable с PHP 8.0.
ВозвратДекодированная строка либо false при ошибке; continue может оставить неразобранные фрагменты.
ГраницыНе mail-body parser, не HTML-экранирование, не проверка подписи/адреса.
Открыть оболочку encoded-word
  1. =?UTF-8?Q?…?= сообщает UTF-8 и способ Q
  2. Декодер извлекает исходные байты
  3. Переводит их в целевую encoding
  4. Возвращается обычный текст для дальнейшей обработки

Почтовый сервис вернул тему вида =?UTF-8?Q?...?=. Это не сломанный русский текст: перед нами отдельный синтаксис MIME. iconv_mime_decode читает метку кодировки в encoded-word, извлекает байты и переводит результат в явно заданную кодировку. Не нужно сначала угадывать её через mb_detect_encoding: внешняя упаковка обычно состоит из ASCII и сама содержит нужное описание.

Прочитать Q-encoded темуСамостоятельный пример
<?php
declare(strict_types=1);

$field = 'Subject: =?UTF-8?Q?=D0=A7=D0=B0=D0=B9?=';
$decoded = iconv_mime_decode($field, ICONV_MIME_DECODE_STRICT, 'UTF-8');
if ($decoded === false) { throw new RuntimeException('Неверная тема'); }
echo $decoded, "\n";
Ожидаемый результат
Subject: Чай

Имя Subject остаётся в результате, если было во входе. Можно декодировать и только значение. Q-encoded слово — не произвольная строка для quoted_printable_decode: нужно также разобрать оболочку и применить заявленную кодировку.

Соединить соседние encoded-word после переносаСамостоятельный пример
<?php
declare(strict_types=1);

$value = "=?UTF-8?B?0KfQsNC5?=\r\n =?UTF-8?B?INGBINC80Y/RgtC+0Lk=?=";
echo iconv_mime_decode($value, ICONV_MIME_DECODE_STRICT, 'UTF-8'), "\n";
Ожидаемый результат
Чай с мятой

Пробел, который является частью фразы, находится внутри второго encoded-word. Служебные пробелы/перенос между соседними encoded-word не следует вручную удалять или добавлять: декодер разбирает структуру.

modeЧто меняется
0Стандартный более снисходительный разбор. Режим оставлен ради распространённых нестрогих заголовков.
ICONV_MIME_DECODE_STRICT (1)Следовать RFC 2047 строже. Это не проверка всех правил письма, адреса или подлинности отправителя.
ICONV_MIME_DECODE_CONTINUE_ON_ERROR (2)Продолжить даже при некорректном поле/фрагменте. Результат может сохранить недекодированное содержимое.
STRICT | CONTINUE_ON_ERROR (3)Битовое ИЛ объединяет оба поведения. Цифру 3 лучше не писать вместо понятных именованных флагов.
Отличить резервное отображение от успешной проверкиСамостоятельный пример
<?php
declare(strict_types=1);

$broken = 'Subject: =?NO-SUCH?B?QQ==?= tail';
$fallback = iconv_mime_decode($broken,
    ICONV_MIME_DECODE_STRICT | ICONV_MIME_DECODE_CONTINUE_ON_ERROR, 'UTF-8');
echo $fallback === $broken ? "Фрагмент оставлен без декодирования\n" : "Иной результат\n";
Ожидаемый результат
Фрагмент оставлен без декодирования

Это пример аварийного просмотра, не правило приёма бизнес-данных. Нельзя считать строковый результат доказательством, что заголовок полностью корректен; continue допускает частичный разбор. В интерфейсе результат всё равно экранируют для HTML.

Параметр encoding — целевая кодировка результата, null/пропуск использует effective internal_encoding; nullable с PHP 8.0. Источник каждого encoded-word может иметь свою метку. При отказе без continue возвращается false, возможны warning/notice о неизвестном charset или повреждённом формате. Функция не анализирует MIME-дерево тела/вложений и не превращает From в подтверждённую личность.

Рядом: iconv_mime_encode, iconv_mime_decode_headers.

Первый опыт с байтами и кодировкой · Ошибки, потери и границы гарантий.

iconv_mime_decode_headersРазобрать блок MIME-заголовков в массив, сохранив повторения и явно обработав неоднозначность.

PHP 5+ · iconv

iconv_mime_decode_headers(string $headers, int $mode = 0, ?string $encoding = null): array|false
Параметр / результатЧто передать и проверить
headersСтрока блока заголовков с переносами; тело после пустой строки не разбирается.
mode0; STRICT; CONTINUE_ON_ERROR; их комбинация через |.
encodingЦелевая кодировка значений, null → internal; nullable с PHP 8.0.
ВозвратИмя поля → строка; повторяющееся точное имя → массив строк. Имена сохраняют регистр; false при ошибке.
Ошибки / ограниченияДиагностика при ошибке формата/кодировки. Не валидатор письма, доверия отправителю или числа Subject.
Блок → группы значений
  1. X-Tag: tea и X-Tag: mint
  2. Один ключ X-Tag → две строки
  3. x-tag может быть отдельным ключом
  4. Нормализуем имена, сохраняя все значения

Из нескольких строк заголовка требуется получить тему и служебные поля как массив. iconv_mime_decode_headers разделяет имена/значения и декодирует encoded-word. foreach затем перебирает этот массив как обычные данные. За первой пустой строкой начинается тело письма: этот API не предназначен для его разбора.

Получить тему из маленького блока заголовковСамостоятельный пример
<?php
declare(strict_types=1);

$raw = "Subject: =?UTF-8?B?0KfQsNC5?=\r\nX-Order: 42\r\n\r\nBody: ignored";
$headers = iconv_mime_decode_headers($raw, ICONV_MIME_DECODE_STRICT, 'UTF-8');
if ($headers === false) { throw new RuntimeException('Заголовки не прочитаны'); }
echo $headers['Subject'], '/', $headers['X-Order'], "\n";
var_dump(array_key_exists('Body', $headers));
Ожидаемый результат
Чай/42
bool(false)

Возвращается ассоциативный массив, не объект сообщения. Ключи из примера фиксированы, но реальные заголовки могут отсутствовать/повторяться: следующий опыт показывает, почему прямого $headers["Subject"] недостаточно.

Сохранить повторения при нормализации имёнСамостоятельный пример
<?php
declare(strict_types=1);

$raw = "X-Tag: tea\r\nX-Tag: mint\r\nx-tag: sale\r\nSubject: One\r\nsubject: Two\r\n";
$decoded = iconv_mime_decode_headers($raw, ICONV_MIME_DECODE_STRICT, 'UTF-8');
if ($decoded === false) { throw new RuntimeException('Неверный блок'); }
$fields = [];
foreach ($decoded as $name => $values) {
    $key = strtolower($name); // В этом формате имена полей ASCII.
    foreach ((array) $values as $value) { $fields[$key][] = $value; }
}
echo implode(', ', $fields['x-tag']), "\n";
echo count($fields['subject'] ?? []) === 1 ? "Одна тема\n" : "Неоднозначная тема\n";
Ожидаемый результат
tea, mint, sale
Неоднозначная тема

Одинаково написанное имя повторяется массивом; имя с другим регистром может стать отдельным ключом. (array) превращает одну строку в одноэлементный массив, чтобы общий цикл сохранил все значения. HTTP/email-имена обычно сравнивают без регистра, поэтому array_change_key_case без накопления мог бы затереть одно значение.

Сохранён порядок значений повторённого точного ключа; исходный общий порядок разных полей после группировки уже не является полным wire-представлением. Не используйте этот преобразованный массив вместо исходных байтов при проверке DKIM/подписи. Для строгого бизнес-правила отдельно проверьте обязательные поля, количество значений и допустимое содержание. Ни mode=STRICT, ни удачное чтение Subject не подтверждают происхождение письма.

mode имеет те же два флага, что iconv_mime_decode; continue допускает частичный результат, поэтому не подходит как единственная проверка приёма. encoding задаёт выходную кодировку и nullable с PHP 8.0. Неверный блок/конвертация может дать false и диагностику. Для больших входных писем ограничьте размер заголовочного блока ещё на этапе чтения; функция получает уже целую строку и создаёт массив.

Рядом: iconv_mime_decode, iconv_mime_encode.

Первый опыт с байтами и кодировкой · Ошибки, потери и границы гарантий.

iconv_get_encodingДиагностировать эффективные кодировки текущего процесса, не принимая настройки за анализ данных.

PHP 4.0.5+ · iconv

iconv_get_encoding(string $type = "all"): array|string|false
Параметр / результатЧто передать и проверить
typeall либо input_encoding / output_encoding / internal_encoding.
Возвратall → массив трёх строк; один известный ключ → строка; неизвестный → false.
Ошибки / версииAPI с PHP 4.0.5; не изменяет настройки. Deprecated с 5.6 именно iconv.*_encoding-директивы, getter не объявлен deprecated.
Две ступени диагностики
  1. ini_get читает сырой iconv override
  2. Пустой override передаёт выбор общим настройкам
  3. iconv_get_encoding сообщает эффективное значение

На одном сервере код читает русское название правильно, на другом считает иначе. Сначала нужно увидеть эффективную настройку текущего PHP-процесса. iconv_get_encoding ничего не перекодирует: он возвращает строку настройки либо массив трёх значений. Это удобно при разборе старого проекта, который опускает аргумент encoding.

Прочитать настройку и отличить неизвестный ключСамостоятельный пример
<?php
declare(strict_types=1);

echo iconv_get_encoding('internal_encoding'), "\n";
var_dump(iconv_get_encoding('not_a_setting'));
echo implode(', ', array_keys(iconv_get_encoding('all'))), "\n";
Ожидаемый результат
UTF-8
bool(false)
input_encoding, output_encoding, internal_encoding

UTF-8 — результат настройки этой лаборатории, не обещание любого сервера. Неизвестный ключ возвращает false. Массив содержит входную, выходную и внутреннюю настройку, но не список поддерживаемых кодировок.

Увидеть разницу пустого override и действующей кодировкиСамостоятельный пример
<?php
declare(strict_types=1);

$old = ini_get('internal_encoding');
try {
    ini_set('internal_encoding', 'ISO-8859-1');
    echo ini_get('iconv.internal_encoding') === '' ? "Iconv override пуст\n" : "Iconv override задан\n";
    echo iconv_get_encoding('internal_encoding'), "\n";
    echo iconv_strlen("\xE9"), "\n";
} finally { ini_set('internal_encoding', $old); }
Ожидаемый результат
Iconv override пуст
ISO-8859-1
1

В опыте iconv.internal_encoding изначально пуст, как в проектной ini. Используется общий internal_encoding, затем настройка восстанавливается. Если legacy override непуст, он имеет приоритет: поэтому одной проверки default_charset при диагностике недостаточно.

Допустимы all (по умолчанию), input_encoding, output_encoding, internal_encoding. В PHP 8.5 сравнение type нечувствительно к регистру, но в коде удобнее придерживаться документированных имён. Для одного ключа результат string, для all — array, для неизвестного — false; проверка только is_array не подходит всем режимам. Неподходящий PHP-тип в strict_types вызывает TypeError.

В PHP 8.5 эффективное значение берётся в порядке: непустое iconv.*_encoding → соответствующее Core *_encoding → default_charset → UTF-8. getter показывает именно это итоговое значение; ini_get показывает сырую строку выбранной директивы. Настройка не переписывает уже существующие переменные и не проверяет реальную кодировку их байтов. Полное пояснение трёх deprecated-директив — в разделе конфигурации.

Рядом: iconv_set_encoding, iconv_strlen, ob_iconv_handler.

Первый опыт с байтами и кодировкой · Ошибки, потери и границы гарантий.

iconv_set_encodingПонять и заменить legacy-изменение общей кодировки, не принимая успешную запись за проверку конвертера.

PHP 4.0.5+ · iconv

iconv_set_encoding(string $type, string $encoding): bool
Параметр / результатЧто передать и проверить
typeinput_encoding, output_encoding или internal_encoding. Другие значения возвращают false.
encodingИмя кодировки; пустое значение снимает override. Поддержка backend при записи не проверяется.
Возвратtrue — настройка записана; false — запись не выполнена. Данные не преобразуются.
Ошибки / версииС PHP 4.0.5; запись iconv.*_encoding deprecated с 5.6 и даёт E_DEPRECATED. PHP 8.5 также проверяет максимальную длину имени.
Изменение контекста, а не текста
  1. setter запоминает строку encoding
  2. Последующие вызовы без encoding читают её
  3. Существующие строки остаются теми же байтами
  4. Явный encoding в вызове снимает эту зависимость

В старом приложении можно встретить iconv_set_encoding перед обработкой текста. Вызов меняет общую настройку Iconv для оставшегося выполнения запроса/процесса. Другой код, опустивший encoding, начнёт зависеть от этого изменения. Для нового библиотечного кода проще и безопаснее передавать нужную кодировку явно в конкретную функцию.

Локально исследовать старый setter и восстановить настройкуСамостоятельный пример
<?php
declare(strict_types=1);

$old = ini_get('iconv.internal_encoding');
$deprecations = 0;
set_error_handler(static function (int $level, string $message) use (&$deprecations): bool {
    if ($level === E_DEPRECATED && str_contains($message, 'iconv.internal_encoding')) {
        ++$deprecations; return true;
    }
    return false;
});
try {
    $changed = iconv_set_encoding('internal_encoding', 'UTF-8');
    $effective = iconv_get_encoding('internal_encoding');
} finally {
    iconv_set_encoding('internal_encoding', $old);
    restore_error_handler();
}
var_dump($changed);
echo $effective, "\n";
echo $deprecations > 0 ? "Устаревшая директива диагностирована\n" : "Нет диагностики\n";
Ожидаемый результат
bool(true)
UTF-8
Устаревшая директива диагностирована

Перехватывается только известное предупреждение этой директивы; исходная настройка восстанавливается в finally. Это лабораторный приём, не совет отключить E_DEPRECATED в рабочем проекте.

Показать, что true не подтверждает поддержку charsetСамостоятельный пример
<?php
declare(strict_types=1);

$old = ini_get('iconv.internal_encoding');
set_error_handler(static function (int $level, string $message): bool {
    return $level === E_DEPRECATED && str_contains($message, 'iconv.internal_encoding');
});
try {
    var_dump(iconv_set_encoding('unknown_key', 'UTF-8'));
    var_dump(iconv_set_encoding('internal_encoding', 'NO-SUCH-CHARSET'));
    echo iconv_get_encoding('internal_encoding'), "\n";
} finally {
    iconv_set_encoding('internal_encoding', $old);
    restore_error_handler();
}
Ожидаемый результат
bool(false)
bool(true)
NO-SUCH-CHARSET

Setter сохраняет имя, но не создаёт конвертер и не проверяет возможность работы с ним. Следующий настоящий вызов может отказать. Поэтому ввод charset не принимают произвольно от посетителя и не проверяют одним setter.

type выбирает только input_encoding, output_encoding или internal_encoding, без префикса iconv.; all здесь не разрешён. encoding — строка имени; пустая строка убирает legacy override и включает fallback. Неизвестный type возвращает false. В PHP 8.5 имя длиной 64 байта и более отклоняется предупреждением и false; это внутренний предел имени, не длина преобразуемого текста. Ошибочный PHP-тип даёт TypeError при strict_types.

Предпочтительная миграция: убрать зависимость отдельной операции от глобальной настройки, передав UTF-8 явно; затем проверить общую конфигурацию приложения и убрать iconv.* overrides. Замена всех вызовов на ini_set(default_charset,...) без анализа тоже меняет общий контекст и может затронуть вывод. Настройки процесса устанавливают в одном известном месте, а не внутри каждого валидатора.

Рядом: iconv_get_encoding, iconv, ob_iconv_handler.

Первый опыт с байтами и кодировкой · Ошибки, потери и границы гарантий.

ob_iconv_handlerПреобразовать вывод на границе legacy-ответа и не спутать внутренний обработчик с обычной функцией.

PHP 4.0.5+; internal alias в PHP 8.5.11 · iconv

PHP 8.5: ob_start("ob_iconv_handler"); исторический договор: (string $contents, int $status): string
Параметр / результатЧто передать и проверить
ПодключениеСпециальное имя передаётся ob_start; прямой callable в проверенной PHP 8.5.11 отсутствует.
contents / statusИсторическая callback-модель: байты порции и битовые флаги output control, поставляемые системой. Не параметры ручного вызова.
НастройкиEffective internal_encoding → output_encoding; legacy iconv overrides deprecated, предпочитайте Core/единый UTF-8.
РезультатПреобразованные байты идут следующему буферу/клиенту при flush; ob_get_clean возвращает исходное содержимое своего буфера.
Ошибки / окружениеКонфликты обработчиков, отправленный вывод, ошибка charset/порции. CLI не проверяет реальный HTTP charset. Страница API существует с PHP 4.0.5.
Вывод меняется на границе
  1. Шаблон echo → UTF-8
  2. Буфер собирает текст
  3. Alias переводит в Windows-1251
  4. Клиент должен читать именно Windows-1251

Старый HTML-шаблон печатает UTF-8, но конкретный legacy-потребитель требует Windows-1251. Обработчик выходного буфера позволяет преобразовать весь вывод на границе ответа. Начните с модели: echo пишет в буфер; при передаче буфера наружу выполняется конвертация; потребитель получает новые байты. Это более широкое действие, чем iconv одной выбранной строки, поэтому для нового приложения обычно проще держать весь HTML в UTF-8.

Два буфера в лаборатории
  1. echo печатает UTF-8 во внутренний ob_iconv_handler
  2. ob_end_flush передаёт его результат во внешний обычный буфер
  3. Внешний ob_get_clean забирает уже сконвертированные байты
  4. bin2hex показывает результат без браузера и HTTP
Подключить alias и получить преобразованный выводСамостоятельный пример
<?php
declare(strict_types=1);

$oldInternal = ini_get('internal_encoding');
$oldOutput = ini_get('output_encoding');
ini_set('internal_encoding', 'UTF-8');
ini_set('output_encoding', 'Windows-1251');
ob_start();
ob_start('ob_iconv_handler');
echo 'Привет';
$active = in_array('ob_iconv_handler', ob_list_handlers(), true);
ob_end_flush();
$converted = ob_get_clean();
ini_set('internal_encoding', $oldInternal);
ini_set('output_encoding', $oldOutput);
var_dump($active, function_exists('ob_iconv_handler'));
echo bin2hex($converted), "\n";
Ожидаемый результат
bool(true)
bool(false)
cff0e8e2e5f2

Пример начинается до любого вывода, iconv.* overrides в проектной ini пусты. Меняются только настройки этого изолированного процесса, затем восстанавливаются. Внешний буфер позволяет исследовать преобразование в CLI; работа HTTP-заголовков этим не проверяется.

Отличить очистку исходного буфера от получения результата обработчикаСамостоятельный пример
<?php
declare(strict_types=1);

$oldInternal = ini_get('internal_encoding');
$oldOutput = ini_get('output_encoding');
ini_set('internal_encoding', 'UTF-8');
ini_set('output_encoding', 'Windows-1251');
ob_start('ob_iconv_handler');
echo 'Чай';
$raw = ob_get_clean();
ini_set('internal_encoding', $oldInternal);
ini_set('output_encoding', $oldOutput);
echo bin2hex($raw), "\n";
echo $raw === 'Чай' ? "Получено исходное содержимое буфера\n" : "Другой результат\n";
Ожидаемый результат
d0a7d0b0d0b9
Получено исходное содержимое буфера

ob_get_clean возвращает исходное содержимое своего буфера, а обработанный вывод при clean не становится его возвращаемым значением. Поэтому попытка использовать его как iconv-обёртку часто ничего не преобразует. Для одной строки проще явный iconv.

Исторические contents и status относятся к договору output handler: содержимое — строка текущей порции; status — битовые флаги этапа обработки, а не HTTP-код и не просто true/false. Их передаёт система буферизации. Для пользовательских callable-обработчиков результат возвращается в следующую ступень вывода; у этого alias внутренний механизм не вызывают вручную.

Источник берётся из эффективной internal_encoding, назначение — output_encoding. Настройте их до запуска/первого вывода, согласуйте Content-Type/charset реального ответа. PHP 8.5 пытается обновить charset у текстового/стандартного Content-Type; если вывод уже отправлен, обработчик может отказать. Следующий пример проверен отдельным HTTP-запросом к локальному PHP-серверу: заголовок и байты ответа согласованы. Это не проверка всех SAPI, сжатия или настроек прокси.

Теперь откроем результат в браузере. Этот небольшой PHP-файл отдаёт страницу старому потребителю, который ожидает Windows-1251. Сам файл остаётся в UTF-8. Сохраните его как example.php, запустите php -S 127.0.0.1:8080 в его каталоге и откройте http://127.0.0.1:8080/example.php. Нужны расширение iconv и пустые устаревшие настройки iconv.internal_encoding/iconv.output_encoding: иначе они перекроют общие настройки ниже.

От строки в PHP до букв на экране
  1. В файле: Заказ №42 в UTF-8
  2. echo направляет HTML в буфер
  3. ob_end_flush передаёт буфер обработчику Iconv
  4. Ответ: байты Windows-1251 + Content-Type с тем же charset
  5. Браузер читает указанный charset и показывает Заказ №42
Страница заказа в браузере с согласованной кодировкойПример для указанного окружения
<?php
declare(strict_types=1);

ini_set('internal_encoding', 'UTF-8');
ini_set('output_encoding', 'Windows-1251');
header('Content-Type: text/html; charset=Windows-1251');

ob_start('ob_iconv_handler');
echo "<h1>Заказ №42</h1>\n";
echo "<p>Чай с мятой — 2 упаковки.</p>\n";
ob_end_flush();
Ожидаемый результат
HTTP 200
Content-Type: text/html; charset=Windows-1251

На странице:
Заказ №42
Чай с мятой — 2 упаковки.

header задаёт описание ответа до его отправки. ob_start включает буферизацию, echo наполняет буфер, ob_end_flush передаёт результат наружу через обработчик. Настройки действуют в данном запросе. Сам заголовок не конвертирует текст: если убрать обработчик, браузер прочитает UTF-8-байты как Windows-1251 и покажет неверные буквы. Для нового сайта согласуйте UTF-8 во всех частях и не добавляйте такую конвертацию без требования внешнего потребителя.

Так выглядит содержимое ответа в браузере

Заказ №42

Чай с мятой — 2 упаковки.

Если увидели непонятные символы, сравните три вещи: кодировку сохранённого PHP-файла, фактические байты тела и charset заголовка. В инструментах браузера заголовок находится в Network → запрос example.php → Response Headers. Изменение одной надписи charset без изменения байтов проблему не исправляет. Подробнее: как работают буферы вывода.

Не включайте конвертацию для бинарного файла, уже закодированного фрагмента, UTF-8 JSON и смешанного потока. Не складывайте несколько одинаковых обработчиков либо ob_iconv_handler с mb_output_handler: предусмотрена проверка конфликтов. Порционный flush может разрезать многобайтную последовательность; этот обработчик не обещает сохранять недописанный символ между произвольными порциями. Для небольшого legacy-текста можно завершить цельный буфер; для больших потоков нужен подходящий потоковый конвертер с проверкой ошибок. Ошибка преобразования выдаёт диагностику и может лишить ответ данных — частично отправленный HTTP-ответ уже нельзя надёжно «откатить».

Рядом: iconv, iconv_get_encoding, iconv_set_encoding.

Первый опыт с байтами и кодировкой · Ошибки, потери и границы гарантий.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.