composer / pcre
提供类型安全 preg_* 替换的 PCRE 封装库。
Requires
- php: ^7.4 || ^8.0
Requires (Dev)
- phpstan/phpstan: ^1.11.10
- phpstan/phpstan-strict-rules: ^1.1
- phpunit/phpunit: ^8 || ^9
Conflicts
- phpstan/phpstan: <1.11.10
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。
安装
使用以下命令安装最新版本
$ 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_grep
和 preg_split
只能通过 Preg
类调用,因为它们没有复杂的结果类型,不需要特定的结果对象。
请参阅MatchResult、MatchWithOffsetsResult、MatchAllResult、MatchAllWithOffsetsResult和ReplaceResult类的源代码以获取更多详细信息。
限制/局限性
由于类型安全要求,存在一些限制。
- 使用
PREG_OFFSET_CAPTURE
进行匹配可通过matchWithOffsets
和matchAllWithOffsets
实现。您不能将标志传递给match
/matchAll
。 Preg::split
也将拒绝PREG_SPLIT_OFFSET_CAPTURE
,您应改用splitWithOffsets
。matchAll
拒绝PREG_SET_ORDER
,因为它也会改变返回匹配项的形状。没有提供替代方案,因为您可以很容易地编写代码来处理它。preg_filter
不受支持,因为它有一个相当疯狂的应用程序接口,您可能最好结合使用一些循环和Preg::replace
来使用Preg::grep
。replace
、replaceCallback
和replaceCallbackArray
不支持数组$subject
,只支持简单字符串。- 从2.0版本开始,该库始终使用
PREG_UNMATCHED_AS_NULL
进行匹配,这提供了更合理/更可预测的结果。从3.0版本开始,此标志也用于replaceCallback
和replaceCallbackArray
。
PREG_UNMATCHED_AS_NULL
从2.0版本开始,此库始终使用PREG_UNMATCHED_AS_NULL对所有match*
和isMatch*
函数进行匹配。从3.0版本开始,这也适用于replaceCallback
和replaceCallbackArray
。
这意味着您的匹配项将始终包含所有匹配的组,要么是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文件。