Text\Emoji: 4-байтовые символы в колонках utf8
Проблема/контекст
Колонка в кодировке utf8 (utf8mb3) в MySQL хранит максимум три байта на символ. Эмодзи и часть CJK-расширений занимают четыре байта, поэтому запись обрывается на первом таком символе и остаток текста теряется. Переводить рабочую базу в utf8mb4 ради одного поля дорого, и ядро идёт другим путём — классом Bitrix\Main\Text\Emoji.
Решение с кодом
Emoji::encode() прогоняет строку через preg_replace_callback и заменяет каждую четырёхбайтовую последовательность на ":" . bin2hex($match) . ":". Кодируются байты UTF-8, а не кодовая точка: 😊 (U+1F60A) даёт :f09f988a: — 10 символов вместо 4 байт. Паттерн покрывает плоскости 1–16 (\xF0[\x90-\xBF], [\xF1-\xF3], \xF4[\x80-\x8F]), трёхбайтовые символы не затрагиваются.
Emoji::decode() работает по собственной регулярке /:([A-F0-9]{8}):/isu и возвращает подстроку без изменений, если hex2bin() не дал валидную четырёхбайтовую последовательность. Обратная сторона: строка :f09f988a:, набранная пользователем руками, при чтении станет эмодзи.
Ключевая деталь модификаторов: getSaveModificator() и getFetchModificator() ничего не преобразуют. Каждый возвращает массив колбэков — [[Emoji::class, 'encode']] и [[Emoji::class, 'decode']]. В параметр поля кладут именно фабрику: Field::getSaveDataModifiers() вызывает её через call_user_func() и бросает SystemException, если вернулся не массив. Так объявлены поля в Bitrix\Blog\PostTable, Bitrix\Vote\QuestionTable, моделях im и landing:
'TITLE' => [
'data_type' => 'string',
'save_data_modification' => [\Bitrix\Main\Text\Emoji::class, 'getSaveModificator'],
'fetch_data_modification' => [\Bitrix\Main\Text\Emoji::class, 'getFetchModificator'],
],
В объектной карте полей удобнее добавлять колбэки напрямую — они проходят проверку is_callable без фабрики:
<?php
declare(strict_types=1);
namespace Vendor\Module\Model;
use Bitrix\Main\ORM\Data\DataManager;
use Bitrix\Main\ORM\Fields\IntegerField;
use Bitrix\Main\ORM\Fields\StringField;
use Bitrix\Main\ORM\Fields\TextField;
use Bitrix\Main\Text\Emoji;
final class CommentTable extends DataManager
{
public static function getTableName(): string
{
return 'vendor_module_comment';
}
public static function getMap(): array
{
return [
(new IntegerField('ID'))->configurePrimary()->configureAutocomplete(),
(new StringField('TITLE'))
->addSaveDataModifier([Emoji::class, 'encode'])
->addFetchDataModifier([Emoji::class, 'decode']),
(new TextField('MESSAGE'))
->addSaveDataModifier([Emoji::class, 'encode'])
->addFetchDataModifier([Emoji::class, 'decode']),
];
}
}
Save-модификаторы применяются в DataManager в add(), update() и их multi-вариантах через Field::modifyValueBeforeSave(). Fetch-модификаторы собирает Query::isFetchModificationRequired() и применяет к алиасам выборки, то есть только к полям, попавшим в select.
Границы механизма:
- фильтр не модифицируется, поиск по значению с эмодзи требует ручного вызова:
CommentTable::getList([
'filter' => ['=TITLE' => Emoji::encode($userInput)],
]);
- закодированное значение длиннее исходного, для
varcharзакладывайте запас; - сырые запросы через
Connectionмодификаторы не проходят — вызывайтеEmoji::encode()/decode()сами; - флаг колонки читается через
Application::getConnection()->isUtf8mb4($table, $column)из секцииutf8mb4вconnections. Компоненты соцсети кодируют текст только приfalse, но ORM-модификаторы срабатывают всегда, независимо от этой настройки.
Итог
Text\Emoji держит четырёхбайтовые символы в трёхбайтовой колонке ценой десяти ASCII-символов на эмодзи. Подключайте модификаторы в карте полей, а фильтры и прямые SQL-запросы кодируйте вручную.
Комментарии (0)
Пожалуйста, войдите в аккаунт, чтобы оставить комментарий
Оставить комментарийЗагрузка...
Пока нет ни одного комментария. Будьте первым!