PHP 8.4: Новая функция grapheme_str_split

Источник: «PHP 8.4: New grapheme_str_split function»
Расширение Intl в PHP 8.4 добавляет новую функцию grapheme_str_split, разбивающую заданную строку на массив графем.

Графема — наименьшая содержательная и функциональная единица языковой системы. Для сравнения, функция mb_str_split из расширения Mbstring имеет схожую семантику, но с существенным отличием в том, что функция mb_str_split разбивает строку на многобайтовые символы Unicode, а функция grapheme_str_split разбивает на функциональные единицы системы письма.

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

Например, слово на сингальском языке අයේෂ් (по-английски произносится Ayesh) состоит из трёх единиц в сингальской письменности: + යේ + ෂ්. — это отдельный символ, а යේ и ෂ් используют дополнительные кодовые точки Юникода в качестве модификаторов гласных. grapheme_str_split правильно разбивает слово на отдельные символы, соответствующие сингальской системе письма, а mb_str_split разбивает его на отдельные кодовые точки Юникода: + + + +.

Ещё несколько примеров на разных языках и Эмодзи:

Строка
Представление в Юникод
Вывод grapheme_str_split
Представление в Юникод
Вывод mb_str_split
Представление в Юникод
PHP
0050 0048 0050
P + H + P
0050 + 0048 + 0050
P + H + P
0050 + 0048 + 0050
你好
4F60 597D
+
4F60 + 597D
+
4F60 + 597D
අයේෂ්
0D85 0DBA 0DDA 0DC2 0DCA
+ යේ + ෂ්
0D85U + 0DBA 0DDA + 0DC2 0DCA
+ + + +
0D85 + 0DBAU + 0DDAU + 0DC2U + 0DCA
สวัสดี
0E2A 0E27 0E31 0E2A 0E14 0E35
+ วั + + ดี
0E2A + 0E27 0E31 + 0E2A 0DCA + 0E2A + 0E14 0E35
+ + + + + ีี
0E2A + 0E27 + 0E31 + 0E2A + 0E14 + 0E35
👭🏻👰🏿‍♂️
1F46D 1F3FB 1F470 1F3FF 200D 2642 FE0F
👭🏻 + 👰🏿‍♂️
1F46D 1F3FB + 1F470 1F3FF 200D 2642 FE0F
👭 + 🏻 + 👰 + 🏿 + + +
1F46D + 1F3FB + 1F470 + 1F3FF + 200D + 2642 + FE0F

Синопсис grapheme_str_split

Функция grapheme_str_split аналогична функции mb_str_split и поддерживает указание параметра int $length для определения длины каждого чанка. Если длина больше, чем длина всей строки или фрагмента графем, то будет возвращена строка/чанк.

Передача пустой строки возвращает пустой массив.

/**
* Splits a string into an array of individual or chunks of graphemes.
*
* @param string $string The string to split into individual graphemes
* or chunks of graphemes.
* @param int $length If specified, each element of the returned array
* will be composed of multiple graphemes instead of a single
* graphemes.
*
* @return array|false
*/

function grapheme_str_split(string $string, int $length = 1): array|false {}

Примеры использования grapheme_str_split

grapheme_str_split("PHP");
// ["P", "H", "P"]

grapheme_str_split("你好");
// ["你", "好"]

grapheme_str_split("你好", length: 4);
// ["你好"]

grapheme_str_split("สวัสดี");
// ["ส", "วั", "ส", "ดี"]

grapheme_str_split("අයේෂ්");
// ["අ", "යේ", "ෂ්"]

grapheme_str_split("👭🏻👰🏿‍♂️");
// ["👭🏻", "👰🏿‍♂️"]

Влияние на обратную совместимость

Новая функция grapheme_str_split является новой в расширении Intl и объявлена в глобальное пространство имён. Если нет существующей функции с таким же именем, это изменение не должно вызвать проблем с обратной совместимостью.

Полифилл grapheme_str_split

Существует возможность создать полифилл функции grapheme_str_split с помощью регулярных выражений Unicode. Селектор /\X/ соответствует полной графеме и может быть использован в качестве основы полифилла.

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

/**
* Splits a string into an array of individual or chunks of graphemes.
*
* @param string $string The string to split into individual graphemes
* or chunks of graphemes.
* @param int $length If specified, each element of the returned array
* will be composed of multiple graphemes instead of a single
* graphemes.
*
* @return array|false
*/

function grapheme_str_split(string $string, int $length = 1): array|false {
if ($length < 0 || $length > 1073741823) {
throw new \ValueError('grapheme_str_split(): Argument #2 ($length) must be greater than 0 and less than or equal to 1073741823.');
}
if ($string === '') {
return [];
}

preg_match_all('/\X/u', $string, $matches);

if (empty($matches[0])) {
return false;
}

if ($length === 1) {
return $matches[0];
}

$chunks = array_chunk($matches[0], $length);

array_walk($chunks, static function(&$value) {
$value = implode('', $value);
});

return $chunks;
}

Дополнительные материалы

Предыдущая Статья

PHP 8.4: Функции array_find, array_find_key, array_any и array_all

Следующая Статья

Руководство по валидации в Laravel