composer/pcre

提供类型安全 preg_* 替换的 PCRE 封装库。

3.3.1 2024-08-27 18:44 UTC

README

提供类型安全 preg_* 替换的 PCRE 封装库。

这个库为你提供了一种确保 preg_* 函数不会静默失败,返回意外的 null 值,这些值可能没有被处理的方法。

从 3.0 版本开始,这个库强制执行所有匹配和 replaceCallback 函数的 PREG_UNMATCHED_AS_NULL 使用,下面了解更多 以了解其影响。

因此,它使得使用静态分析工具(如 PHPStan 或 Psalm)更容易,因为它简化并减少了所有 preg_* 函数的可能的返回值,这些函数包含了许多边缘情况。从 v2.2.0 / v3.2.0 版本开始,这个库还附带了一个 PHPStan 扩展,用于解析正则表达式并为你提供更好的输出类型。

这个库是 preg_* 函数的一个薄包装,有一些 限制。如果你需要一个更丰富的 API 来处理正则表达式,请查看 rawr/t-regx

Continuous Integration

安装

使用以下命令安装最新版本

$ composer require composer/pcre

需求

  • 3.x 版本需要 PHP 7.4.0
  • 2.x 版本需要 PHP 7.2.0
  • 1.x 版本需要 PHP 5.3.2

基本用法

代替

if (preg_match('{fo+}', $string, $matches)) { ... }
if (preg_match('{fo+}', $string, $matches, PREG_OFFSET_CAPTURE)) { ... }
if (preg_match_all('{fo+}', $string, $matches)) { ... }
$newString = preg_replace('{fo+}', 'bar', $string);
$newString = preg_replace_callback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string);
$newString = preg_replace_callback_array(['{fo+}' => fn ($match) => strtoupper($match[0])], $string);
$filtered = preg_grep('{[a-z]}', $elements);
$array = preg_split('{[a-z]+}', $string);

你现在可以调用这些在 Preg 类上

use Composer\Pcre\Preg;

if (Preg::match('{fo+}', $string, $matches)) { ... }
if (Preg::matchWithOffsets('{fo+}', $string, $matches)) { ... }
if (Preg::matchAll('{fo+}', $string, $matches)) { ... }
$newString = Preg::replace('{fo+}', 'bar', $string);
$newString = Preg::replaceCallback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string);
$newString = Preg::replaceCallbackArray(['{fo+}' => fn ($match) => strtoupper($match[0])], $string);
$filtered = Preg::grep('{[a-z]}', $elements);
$array = Preg::split('{[a-z]+}', $string);

主要区别是如果匹配/替换失败,它将抛出 Composer\Pcre\PcreException 异常而不是返回 null(或在某些情况下返回 false),因此你现在可以安全地使用返回值,依赖于它们只能是字符串(对于替换),整数(对于匹配)或数组(对于 grep/split)。

此外,Preg 类还提供了一些返回 bool 而不是 int 的匹配方法,以便在模式匹配的数量无意义时提供更严格的类型安全。

use Composer\Pcre\Preg;

if (Preg::isMatch('{fo+}', $string, $matches)) // bool
if (Preg::isMatchAll('{fo+}', $string, $matches)) // bool

最后,Preg 类提供了一些 *StrictGroups 方法变体,确保匹配组始终存在,因此是非空的,这使得编写类型安全代码更容易。

use Composer\Pcre\Preg;

// $matches is guaranteed to be an array of strings, if a subpattern does not match and produces a null it will throw
if (Preg::matchStrictGroups('{fo+}', $string, $matches))
if (Preg::matchAllStrictGroups('{fo+}', $string, $matches))

注意:只要你不使用可选子模式(即 (something)?(something)* 或由 | 引起的分支导致某些组根本不匹配),这通常是安全的。可以匹配空字符串的子模式(如 (.*))不是可选的,它将以空字符串的形式出现在匹配中。即使是非匹配的子模式,即使它是可选的(如 (?:foo)?),它也不会出现在匹配中,因此与 *StrictGroups 方法一起使用也不是问题。

如果你更喜欢稍微更冗长的用法,将引用参数替换为结果对象,你可以使用 Regex

use Composer\Pcre\Regex;

// this is useful when you are just interested in knowing if something matched
// as it returns a bool instead of int(1/0) for match
$bool = Regex::isMatch('{fo+}', $string);

$result = Regex::match('{fo+}', $string);
if ($result->matched) { something($result->matches); }

$result = Regex::matchWithOffsets('{fo+}', $string);
if ($result->matched) { something($result->matches); }

$result = Regex::matchAll('{fo+}', $string);
if ($result->matched && $result->count > 3) { something($result->matches); }

$newString = Regex::replace('{fo+}', 'bar', $string)->result;
$newString = Regex::replaceCallback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string)->result;
$newString = Regex::replaceCallbackArray(['{fo+}' => fn ($match) => strtoupper($match[0])], $string)->result;

注意,preg_greppreg_split 只能通过 Preg 类调用,因为它们没有复杂的结果类型,不需要特定的结果对象。

请参阅MatchResultMatchWithOffsetsResultMatchAllResultMatchAllWithOffsetsResultReplaceResult类的源代码以获取更多详细信息。

限制/局限性

由于类型安全要求,存在一些限制。

  • 使用PREG_OFFSET_CAPTURE进行匹配可通过matchWithOffsetsmatchAllWithOffsets实现。您不能将标志传递给match/matchAll
  • Preg::split也将拒绝PREG_SPLIT_OFFSET_CAPTURE,您应改用splitWithOffsets
  • matchAll拒绝PREG_SET_ORDER,因为它也会改变返回匹配项的形状。没有提供替代方案,因为您可以很容易地编写代码来处理它。
  • preg_filter不受支持,因为它有一个相当疯狂的应用程序接口,您可能最好结合使用一些循环和Preg::replace来使用Preg::grep
  • replacereplaceCallbackreplaceCallbackArray不支持数组$subject,只支持简单字符串。
  • 从2.0版本开始,该库始终使用PREG_UNMATCHED_AS_NULL进行匹配,这提供了更合理/更可预测的结果。从3.0版本开始,此标志也用于replaceCallbackreplaceCallbackArray

PREG_UNMATCHED_AS_NULL

从2.0版本开始,此库始终使用PREG_UNMATCHED_AS_NULL对所有match*isMatch*函数进行匹配。从3.0版本开始,这也适用于replaceCallbackreplaceCallbackArray

这意味着您的匹配项将始终包含所有匹配的组,要么是null(如果未匹配),要么是字符串(如果匹配)。

如果比较使用和不使用PREG_UNMATCHED_AS_NULL的$flags运行此代码的两个输出,则清晰度和可预测性的优势将更加明显。

preg_match('/(a)(b)*(c)(d)*/', 'ac', $matches, $flags);

PHPStan 扩展

如果您不使用phpstan/extension-installer,则可以使用PHPStan扩展。您可以将vendor/composer/pcre/extension.neon包含到您的PHPStan配置中。

此扩展提供了对$matches的更好的类型信息,以及尽可能的regex验证。

许可

composer/pcre在MIT许可证下授权,有关详细信息,请参阅LICENSE文件。