PHP 8.4: Обновление PCRE2 и изменения в регулярных выражениях

Источник: «PHP 8.4: PCRE2 Upgrade and Regular Expression Changes»
Обновление PCRE2 в PHP 8.4 содержит ряд изменений, которые могут быть несовместимы с существующими регулярными выражениями или несовместимы с другими версиями движков регулярных выражений.

Возможности PHP по работе с регулярными выражениями, доступные в виде функций preg_*, опираются на библиотеку PCRE (Perl-Compatible Regular Expressions). В версии PHP 7.3 PHP начал использовать PCRE2.

PHP медленно поддерживал незначительные обновления PCRE, такие, как PCRE2 10.39 в 2021 году и PCRE210.40 в 2022 году. Однако PCRE2 10.43 привнесла некоторые значительные изменения, в том числе и изменения, затрагивающие синтаксис регулярных выражений, которые она поддерживает.

Библиотека PCRE2 включена в дерево исходных текстов PHP, поэтому в зависимости от времени компиляции изменений нет.

Обновление PCRE2 в PHP 8.4 содержит ряд изменений, которые могут быть несовместимы с существующими регулярными выражениями или несовместимы с другими версиями движков регулярных выражений.

Изменения синтаксиса регулярных выражений

Далее перечислены изменения в синтаксисе регулярных выражений, внесённые в обновление PCRE2 10.43. Эти изменения вступят в силу в PHP 8.4, так как именно эта версия фактически несёт в себе обновление PCRE2 10.43.

Квантификаторы без минимального количества

До PHP 8.4 выражения без минимального количества считались недействительными. В PHP 8.4 квантификаторы без указанного минимального количества (например, /a{,3}/) считаются с нулевым минимальным количеством (т.е. /a{0,3}/).

В следующем фрагменте показан вызов preg_match с Regexps, который соответствует от нуля до трёх совпадений с символом a.

preg_match('/a{,3}/', 'aaa'); // Допустимо только в PHP 8.4
preg_match('/a{0,3}/', 'aaa'); // Допустимо в PHP 8.4 а ранних версиях

Это изменение синтаксиса соответствует Perl 5.34.0. Python также поддерживает синтаксис {,3}, но другие языки, такие как JavaScript, Go, Java и т. д., его не поддерживают.

Пробелы допустимы в фигурных скобках

PHP 8.4 позволяет использовать символы пробела и горизонтальной табуляции после и перед парами фигурных скобок квантификаторов и вокруг запятой, разделяющей квантификаторы. Это не совместимо с Perl, но ECMAScript такой синтаксис поддерживает.

preg_match('/a{ 5,10 }/',    'aaaaaaa'); // Допустимо только в PHP 8.4
preg_match('/a{5 ,10}/', 'aaaaaaa'); // Допустимо только в PHP 8.4
preg_match('/a{ 5, 10 }/', 'aaaaaaa'); // Допустимо только в PHP 8.4
preg_match('/a{ 5, 10 }/', 'aaaaaaa'); // Допустимо только в PHP 8.4

До версии PHP 8.4/PCRE2 10.43 приведённые выше регекспы не считаются допустимыми квантификаторами и сопоставляются только как строковый литерал.

Обновление Unicode 15

Входящий в состав PHP 8.4 пакет PCRE2 теперь поддерживает Unicode 15. Помимо новых Эмодзи и глифов в Unicode 15, появилась поддержка новых классов символов Unicode.

Например, с обновлением Unicode 15 новые скрипты, добавленные в Unicode 15, можно использовать в качестве классов именованных символов. В Unicode 15 добавлены скрипты Kawi (U11F00-11F5F) и Nag_Mundari (U1E4D0-1E4FF), это означает, что их можно использовать в регекспах:

preg_match('/\p{Kawi}/u', 'abc');
preg_match('/\p{Nag_Mundari}/u', 'abc');

В версиях PHP, предшествующих PHP 8.4, это приводит к предупреждению, поскольку символьные классы Kawi и Nag_Mundari неизвестны PCRE2.

preg_match(): Compilation failed: unknown property after \P or \p at offset ...

Старые версии PHP могут продолжать сопоставлять эти символы и эмодзи, но вместо использования именованных групп соответствия, regexp должен определять диапазон:

preg_match('/\p{Kawi}/u', 'abc');
// эквивалентен:
preg_match('/[\x{11F00}-\x{11F5F}]/u', 'abc');
preg_match('/\p{Nag_Mundari}/u', 'abc');
// эквивалентен:
preg_match('/[\x{1E4D0}-\x{1E4FF}]/u', 'abc');

Кроме того, обновление Unicode 15 привнесёт изменения в существующие классы символов, новые Эмодзи и новый шаблон ZWJ для комбинаций Эмодзи.

Regex \w в режиме Unicode

До версии PHP 8.4 использование символьного класса /\w/u было эквивалентно /[\p{L}\p{N}_]/u. Это означает, что \w является сокращением для класса символов \p{L} ( Unicode "letter" указатель символа), \p{N} (числовой символ в любой системе письма) и подчёркивания (_).

В PHP 8.4 и более поздних версиях \w дополнительно включает \p{Mn} (не пробельный символ) и \p{Pc} (соединительная пунктуация). Это делает \w эквивалентным /[\p{L}\p{N}_\p{Mn}\p{Pc}]/u. Новое поведение соответствует Perl.

Начиная с Unicode 15, категория символов Mn содержит 1 839 записей, а категория Pc — 10 записей. Потенциально это может оказать большее влияние на существующие регекспы, поскольку /w/u теперь соответствует 1 849 дополнительным символам.

preg_match('/\w/u', "\u{0300}"); // PHP < 8.4: Нет соответствия
preg_match('/\w/u', "\u{0300}"); // PHP >= 8.4: Есть соответствие

Поддержка модификатора caseless restrict

Как часть обновления PCRE2 10.43, PHP 8.4 может использовать модификатор "caseless restrict" в регулярных выражениях. При его применении предотвращается сопоставление ASCII и не ASCII символов.

Например, знак Кельвина (K, "\u{212A}") и английская буква K могут быть взаимозаменяемо сопоставлены с k (английская простая буква k) в Unicode Regex:

preg_match('/k/iu', "K"); // Совпадение
preg_match('/k/iu', "k"); // Совпадение
preg_match('/k/iu', "\u{212A}"); // Совпадение

В PHP 8.4 появился режим "caseless restrict", который предотвращает caseless (/i) совпадения между ASCII и не ASCII символами. Этот режим включается установкой символа (?r) в позицию, с которой должно начинаться caseless совпадение. (?-r) также может отключить caseless соответствие.

preg_match('/(?r)k/iu', "K"); // Совпадение
preg_match('/(?r)k/iu', "k"); // Совпадение
preg_match('/(?r)k/iu', "\u{212A}"); // НЕТ Совпадения

Использование модификаторов "caseless restrict" в версиях PHP, предшествующих PHP 8.4, выдаёт предупреждение PHP:

Compilation failed: unrecognized character after (? or (?-

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

Библиотека PCRE2 является частью дерева исходников PHP, и внести эти изменения в старые версии PHP не представляется возможным.

Некоторые функции (например, классы символов, Kawi и Nag_Mundari) можно использовать в старых версиях PHP, указав их диапазон Unicode, но большинство новых функций не могут быть перенесены в старые версии PHP.

Использование константы PCRE_VERSION позволяет получить версию PCRE2, что может быть полезно при выполнении условных вызовов preg_* в зависимости от доступности.


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

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

Декодирование специфичности CSS

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

PHP 8.4: Новые функции http_(get|clear)_last_response_headers