PHP 8.4: Обновление PCRE2 и изменения в регулярных выражениях
Возможности 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_*
в зависимости от доступности.