squirrelphp / strings
PHP中的常用字符串操作:过滤字符串、生成随机字符串、将整数压缩成字符串以及修改URL
Requires
- php: >=8.1
- ext-mbstring: *
- squirrelphp/debug: ^2.0
Requires (Dev)
- ext-intl: *
- bamarni/composer-bin-plugin: ^1.3
- captainhook/plugin-composer: ^5.0
- phpunit/phpunit: ^10.0
- symfony/form: ^5.0|^6.0|^7.0
- symfony/http-foundation: ^5.0|^6.0|^7.0
- twig/twig: ^3.0
Suggests
- squirrelphp/strings-bundle: Symfony integration of squirrelphp/strings
README
处理PHP应用程序中的常用字符串操作
- 过滤字符串(移除换行符、删除多余空格、换行长单词等)
- 测试字符串(字符串是否为有效的UTF8或是否为有效的日期时间格式)
- 生成随机字符串,包含一组字符
- 将数字压缩成字符串,并将字符串转换/扩展为数字
- 处理URL并在安全的方式下修改它(转换为相对URL,更改其部分等)
- Regex包装器以更好地处理最常见的正则表达式函数的类型提示和错误
安装
composer require squirrelphp/strings
或者,可以通过 squirrelphp/strings-bundle 进行安装,以便在Symfony项目中轻松集成,因为它默认提供了许多依赖注入服务,并允许您根据此库轻松注册额外的功能。
过滤字符串
使用 Squirrel\Strings\StringFilterInterface
接口过滤字符串并返回字符串
public function filter(string $string): string;
每个过滤器恰好做一件事情(理想情况下),因此可以根据输入需要如何更改/处理来组合它们。
可以通过实现 Squirrel\Strings\StringFilterInterface
简单地定义额外的过滤器。在应用程序中自定义过滤器的可能想法
- 处理HTML标签,通常与应用程序高度相关(在哪些上下文中允许哪些标签)
- 简化用户输入并将多个过滤器组合成一个过滤器
- 将HTML转换为Markdown,或反之
此库有一些基本的过滤器,可以覆盖很多领域,可以单独使用或组合使用。每个过滤器类都以 Filter
后缀结尾,为了使标题和文本更易读,以下列表中没有提及。
换行符、制表符和空格
规范化换行符并删除多余的空格有助于在存储数据前节省空间,并始终得到一致的数据,特别是如果您希望在以后将空格和换行符转换为HTML或其他格式。
NormalizeNewlinesToUnixStyle
将所有类型的换行符(在Unicode中目前有8种不同的换行符)替换为Unix换行符(\n),这样就不会出现不同换行符的混合以及意外的结果。
ReplaceUnicodeWhitespaces
将所有Unicode空白字符(目前有16种不同)替换为普通空格,如果您不关心空格之间细微的差异(如宽度、非断开或数学),这在大多数输入中都是有意义的,以便能够剪除并限制不必要的空格。
RemoveExcessSpaces
删除任何不必要的空格,这些空格是
- 字符串开头或结尾的任何空格
- Unix换行符周围的任何空格
- 将连续的空格缩减为仅一个空格
仅操作常规空格(unicode 0020,十进制 32)并忽略其他unicode空白字符。
LimitConsecutiveUnixNewlines
限制连续出现的新行数量。这仅处理Unix新行,不考虑新行之间的空格,因此首先运行上述三个过滤器是有意义的。
该过滤器的第一个参数是允许的连续新行数,默认为两个,但可以设置为任何大于零的数字。
RemoveZeroWidthSpaces
零宽度空格通常不是你希望保存在数据库中的内容,因此该过滤器移除了Unicode中定义的三个主要零宽度空格。
ReplaceNewlinesWithSpaces
如果你不需要文本中的任何新行,或者不允许新行,该过滤器将任何类型的新行(在Unicode中目前有8种不同的新行字符)替换为空格(以避免仅由新行分隔的内容组合的风险)。内部首先使用NormalizeNewlinesToUnixStyle过滤器,然后将Unix样式的新行替换为空格。
Trim
从字符串的开始和结束处删除字符,如果传递给构造函数的字符仅是ASCII,则使用PHP trim函数,如果修剪的是Unicode字符,则使用正则表达式。
默认情况下(如果没有使用构造函数参数),修剪的字符与PHP trim函数默认修剪的相同,即:" \t\n\r\0\x0B"(普通空格、水平制表符、换行符、回车符、空字符和垂直制表符)
ReplaceTabsWithSpaces
将所有水平制表符替换为空格。这是唯一处理水平制表符的过滤器,因为制表符可能与其他Unicode空格具有不同的/特定的含义。
WrapLongWordsNoHTML
没有换行符(如空格或换行符)的字符序列可能会破坏布局并且难以显示,这可能会出现在用户输入中,甚至可能在常规内容中意外出现。
该过滤器在未出现Unix新行或普通空格的一定数量的字符(默认为20)后添加一个零宽度空格。因此,如果空间足够,即使长词也不会被分割,但如果空间紧张,长词将被分割成多行。
此过滤器变体假设不允许HTML,因此它可能会分割任何长字符序列。
WrapLongWordsWithHTML
没有换行符(如空格或换行符)的字符序列可能会破坏布局并且难以显示,这可能会出现在用户输入中,甚至可能在常规内容中意外出现。
该过滤器在未出现Unix新行或普通空格的一定数量的字符(默认为20)后添加一个零宽度空格。因此,如果空间足够,即使长词也不会被分割,但如果空间紧张,长词将被分割成多行。
此过滤器变体在字符串中查找HTML标签。如果没有找到,则像WrapLongWordsNoHTML一样行为,如果存在HTML标签,则每个标签临时替换为替代字符,并只计算为一个字符,不会被过滤器分割。因此,当存在许多HTML标签时,单词可能会“过早”分割,因为HTML标签在换行时计为一个字符。
Cases: lowercase, uppercase, camelcase, snakecase
Lowercase
将所有Unicode字符转换为它们的等效小写。
Uppercase
将所有Unicode字符转换为它们的等效大写。
UppercaseFirstCharacter
将字符串的第一个字符转换为大写,正确处理Unicode字符作为第一个字符。
UppercaseWordsFirstCharacter
将字符串中每个单词的第一个字符转换为大写,正确处理Unicode字符。
CamelCaseToSnakeCase
从CamelCase转换为snake_case。仅支持字母数字字符(A-Z、a-z、0-9),忽略所有其他字符!
SnakeCaseToCamelCase
从snake_case转换为CamelCase。仅支持字母数字字符(A-Z、a-z、0-9),忽略所有其他字符!
HTML
RemoveHTMLTags
移除所有HTML标签。如果HTML标签不规范,可能会移除比预期更多的内容,因为它不尝试验证HTML,只是移除任何看起来像HTML标签的内容。
RemoveHTMLTagCharacters
移除HTML标签中使用的三个主要字符:<,>和""
ReplaceUnixStyleNewlinesWithParagraphs
对于HTML,你通常希望以可预测的方式处理换行符,这个过滤器是其中一种可能
- 将双换行符
\n\n
转换为</p><p>
- 将单行换行符
\n
转换为<br/>
- 在字符串开头添加
<p>
并在结尾添加</p>
对于没有块级HTML标签的简单内容,这通常是理想的结构化文本并在HTML页面上显示的方式。
EncodeBasicHTMLEntities
将&"'<>
编码为其HTML实体(&
,"
,'
,<
,>
),这对于在HTML环境中正确和安全地显示文本非常有帮助。
DecodeBasicHTMLEntities
与EncodeBasicHTMLEntities相反,如果你知道输入可能包含HTML实体,并且你想简化文本并避免像&amp;
这样的东西,这是有意义的。
DecodeAllHTMLEntities
根据HTML5标准解码所有HTML实体(内部使用html_entity_decode
)。这通常不是必需的,但如果你知道收到的文本包含很多HTML实体,而且你不知道确切的种类或数量,这可能会很有意义。
移除/限制字符和内容
RemoveNonUTF8Characters
移除任何不符合UTF8规范的字符。这个过滤器建议用于来自你应用外部的任何内容(用户输入、Web服务、数据导入),这样你就可以继续在有效的UTF8字符串上操作。大多数字符串函数或数据库在字符串包含无效字符时将拒绝该字符串。
如果你的应用程序绝对不能出现无效的UTF8字符,那么检查应用程序中的编码并以这种方式抛出异常可能更有意义。
// Checks if $string contains only valid UTF8 characters if (!\mb_check_encoding($string, 'UTF-8')) { // Invalid characters found, log this or throw an exception }
然而,对于通用的用户输入,你可能只想尝试解决问题,即使输入部分损坏(可能比完全失败更好)。
RemoveNonAlphanumeric
移除任何不是字母或数字的字符,因此只允许A-Z、a-z和0-9。对于标记、URL的一部分、输入的代码或其他你知道不允许其他字符的情况,这可能很有用,你只想忽略非字母数字的任何内容。
RemoveNonAlphabetic
移除任何不是字母的字符,因此只允许A-Z和a-z。对于标记、国家或语言代码或其他你知道不允许其他字符的情况,这可能很有用,你只想忽略它们。
RemoveNonNumeric
移除任何不是数字的字符,因此只允许0-9。对于标记、URL的一部分、输入的代码或其他你知道不允许其他字符的情况,这可能很有用,你只想忽略它们。
RemoveNonAsciiAndControlCharacters
移除任何不是字母、数字和基本ASCII字符的字符,因此只允许A-Z、a-z、0-9、空格和!"#$'()*+,-./:;<=>?@[\]^_`{|}~
(不允许换行符、控制字符或unicode - 所有这些都已移除)。
RemoveEmails
移除任何看起来像电子邮件地址的内容,这意味着任何包含非空格字符的字符串部分,在@符号前后。
最初添加是为了能够分析文本并检测语言,因为电子邮件地址可能会使语言检测算法混乱,因此从字符串中移除任何看起来像电子邮件地址的内容应该导致“只有文本”或至少是更可分析的文本。
RemoveURLs
移除任何看起来像URL的内容,这意味着任何看起来像有效方案的字符串部分,后跟"://",后跟零个或多个非空格字符。
最初添加是为了能够分析文本和检测语言,因为URL会干扰语言检测算法,所以从字符串中删除任何看起来像URL的东西应该导致“只有文本”或至少更容易分析的文本。
转换为ASCII
有时Unicode字符众多可能会成为障碍 - 例如在这些情况下
- 在受限客户数据库中,您希望输入的姓氏如
émil
也能与emil
匹配,这样客户就不能稍微更改他的名字来规避您的安全措施 - 当用户输入地址,您要将其与已知地址数据库进行核对时,希望它是用户友好的,因此如果用户输入
Leon Breitling-Strasse
或Leon Breitlingstrasse
,您希望这两个都能匹配Léon Breitling-Strasse
,尽管字符不完全匹配 1:1 - 对于URL,您希望尽可能地将字符映射到ASCII字母,因此标题为
L'école d'Humanité
的博客文章变为l-ecole-d-humanite
(对于URL如https://my-blog.com/2019-07-12/l-ecole-d-humanite
),这对于用户和搜索引擎来说都是可读的
注意:这对于使用拉丁字符的国家和语言(如大多数欧洲、北美、南美、大多数非洲、澳大利亚)效果很好,但对于其他脚本(如西里尔文、阿拉伯文、汉字或希腊文等)效果则不那么好。
NormalizeLettersToAscii
尽可能将大多数字母减少到它们的基拉丁ASCII字符(A-Z,a-z),例如,é变为e,Â变为A等。它非常彻底,并使用Intl扩展的Normalizer以及一系列自定义转换。某些字符被转换为两个ASCII字符(如Æ
=> AE
,或ß
变为ss
),因此您的字符串可能会更长。
NormalizeToAlphanumeric
运行上面的NormalizeLettersToAscii,然后删除任何非字母数字字符,因此
Léon Breitling-Strasse 13
变为LeonBreitlingStrasse13
- 'Pré Raguel Strasse de l'école'变为'PreRaguelStrassedelecole'
NormalizeToAlphanumericLowercase
运行上面的NormalizeToAlphanumeric,然后将所有字符转换为小写,因此
Léon Breitling-Strasse 13
变为leonbreitlingstrasse13
- 'Pré Raguel Strasse de l'école'变为'preraguelstrassedelecole'
如果您以这种方式处理用户输入和数据库中的已知值,您就可以匹配它们并得到更多的匹配/结果,因为空格、连字符、变音符号等不被考虑。
ReplaceNonAlphanumeric
有时您不想删除非字母数字字符,而是用字符替换它们,例如对于URL,您希望将L'école d'Humanité
转换为l-ecole-d-humanite
。
这就是ReplaceNonAlphanumeric
默认执行的操作 - 将所有非字母数字字符替换为连字符,如果多个非字母数字字符连续出现,则只替换为一个连字符。
在ReplaceNonAlphanumeric
的构造函数中,您可以设置一个除连字符之外的替换字符 - 例如,点或斜杠,具体取决于您的用例。
Streamline input
这些过滤器将其他过滤器组合成一个合理的包,以在用户输入上运行,更多的是一个示例,而不是您可能希望直接在应用程序中使用的示例。
您可以通过使用Squirrel\Strings\StringFilterRunner
类来执行自己的过滤器组合。
StreamlineInputWithNewlines
运行以下过滤器
- RemoveNonUTF8Characters
- ReplaceUnicodeWhitespaces
- ReplaceTabsWithSpaces
- NormalizeNewlinesToUnixStyle
- RemoveExcessSpaces
- LimitConsecutiveUnixNewlines
这确保字符串是有效的UTF8,并规范化所有空白字符,同时删除不必要的空白字符,而内容本身保持不变(与HTML一起工作或单独工作,不转换HTML实体)。
StreamlineInputNoNewlines
运行以下过滤器
- RemoveNonUTF8Characters
- ReplaceUnicodeWhitespaces
- ReplaceTabsWithSpaces
- ReplaceNewlinesWithSpaces
- RemoveExcessSpaces
基本与StreamlineInputWithNewlines相同,但换行符会被转换为空格。这对于像姓名、电子邮件地址和其他任何换行符没有意义的普通用户输入很有用。
测试一个字符串
使用Squirrel\Strings\StringTesterInterface
接口测试一个字符串,并返回true或false(测试是否成功)
public function test(string $string): bool;
可以通过实现Squirrel\Strings\StringTesterInterface
轻松定义额外的测试器。在应用程序中自定义测试器的可能想法
- 检查外部数据结构(这可以高度依赖于应用程序)
- 检查字符串是否包含允许的值或允许的字符
此库有两个默认测试器。每个测试器类都以Tester
后缀结尾,以下列表中没有提及,以保持标题和文本的清晰性。
ValidUTF8
检查字符串是否只包含有效的UTF8字符。如果您的应用程序对外部数据要求严格,则拒绝任何包含非UTF8字符的数据是有意义的(处理非UTF8字符的一种不太严格的方法是RemoveNonUTF8Characters过滤器)。
ValidDateTime
根据类构造函数中提供的date
函数接受的日期时间格式(默认为ISO日期格式,年份、月份和日期之间用破折号分隔,即Y-m-d
)检查字符串,并确保给定的日期存在(2021-02-29
将返回false,而2020-02-29
将返回true)。在验证输入时,这可以轻松确保日期符合您期望的格式,并可进一步处理。
生成一个随机字符串
根据字符串中允许的字符列表生成随机字符串。
包含两个类(一个支持unicode,一个只支持ASCII)可以轻松定义一个具有您自己的字符集的随机生成器,这些字符可以出现在随机字符串中。以下是一些合理的值
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
,每个字符有62种可能的值,可以是A-Z、a-z或0-9,这些值在应用程序中使用非常安全(没有特殊字符,只有字母数字)abcdefghijklmnopqrstuvwxyz0123456789
,每个字符有36种可能的值,与上面相同,但这是不区分大小写的版本,用于当应该区分“A”和“a”时(例如)234579ACDEFGHKMNPQRSTUVWXYZ
或234579acdefghkmnpqrstuvwxyz
,每个字符有27个易于阅读的大写或小写字母:如果某人需要输入一个代码,最好避免容易混淆的字符,如0(数字零)和O(字母),或8(数字八)和B(字母)
定义您自己的可能字符范围很容易,甚至可以使用unicode字符。
将字符串压缩为数字
将整数转换为具有给定“字符集”的字符串,这样我们可以将整数编码以压缩它(因此一个包含8个数字的整数现在只是一个4个字符的字符串),并在需要时将其转换回。
主要用例是URL中的令牌,这样就需要更少的空间,因为即使是大数字,如果使用每个字符36或62个可能的值,也会变成短字符串:使用62个可能的字符(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
),一个三字符的字符串可以覆盖多达238,328个数字,五个字符可以覆盖多达916,132,832个数字。
压缩的副作用是它使整数的使用不再那么明显——令牌看起来是随机的,不会泄露它们的意图。
定义您自己的可能字符范围很容易,甚至可以使用unicode字符。
URL
URL类接受一个URL作为构造函数参数,然后允许您获取或更改URL的某些部分以执行以下操作
- 获取方案、主机、路径、查询字符串和特定的查询字符串变量
- 将绝对URL转换为相对URL
- 更改方案、主机、路径和查询字符串
- 替换查询字符串变量,或添加/删除它们
这可以用来轻松构建或更改您的URL,或者用于清理给定的URL的某些部分,例如在重定向时:使用相对URL而不是绝对URL,以避免恶意重定向到您无法控制的地方。
正则表达式包装器
由于使用了引用($matches
)和许多可能的返回值,因此使用内置的 preg_match
、preg_match_all
、preg_replace
和 preg_replace_callback
PHP 函数通常会使代码的可读性降低,难以理解,对于静态分析器来说也是如此。 Squirrel\Strings\Regex
包装了这些 preg 函数的基本功能,创建了更容易理解的返回值,并在出现错误时抛出 Squirrel\Strings\Exception\RegexException
。以下是 Regex 类的可用静态方法
Regex::isMatch(string $pattern, string $subject, int $offset): bool
包装 preg_match
以检查 $pattern
是否存在于 $subject
中。
Regex::getMatches(string $pattern, string $subject, int $flags, int $offset): ?array
包装 preg_match_all
以检索 $subject
中 $pattern
的所有匹配项,始终设置 PREG_UNMATCHED_AS_NULL
标志,并可以添加额外的标志。如果没有找到匹配项,则返回 null,否则返回由 preg_match_all
为 $matches
设置的结果数组。
Regex::replace(string|array $pattern, string|array $replacement, string $subject, int $limit): string
包装 preg_replace
以替换 $pattern
的匹配项为 $replacement
,并且只接受字符串作为 $subject
。
Regex::replaceArray(string|array $pattern, string|array $replacement, array $subject, int $limit): array
包装 preg_replace
以替换 $pattern
的匹配项为 $replacement
,并且只接受数组作为 $subject
。
Regex::replaceWithCallback(string|array $pattern, callback $callback, string $subject, int $limit, int $flags): string
包装 preg_replace_callback
以对每个 $pattern
在 $subject
中的匹配项调用一个回调,该回调具有签名 function(array $matches): string
,并且只接受字符串作为 $subject
。
Regex::replaceArrayWithCallback(string|array $pattern, callback $callback, array $subject, int $limit, int $flags): array
包装 preg_replace_callback
以对每个 $pattern
在 $subject
中的匹配项调用一个回调,该回调具有签名 function(array $matches): string
,并且只接受数组作为 $subject
。